mirror of
https://github.com/oneclickvirt/backtrace.git
synced 2026-09-03 13:15:52 +08:00
feat(dns): prepare backtrace v0.0.21
This commit is contained in:
parent
e83eb23302
commit
2091c5b665
3
.github/workflows/build.yml
vendored
3
.github/workflows/build.yml
vendored
@ -9,9 +9,6 @@ concurrency:
|
||||
group: backtrace-ipv6-prefix-sync
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
|
||||
64
.github/workflows/ci.yaml
vendored
64
.github/workflows/ci.yaml
vendored
@ -1,58 +1,34 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: backtrace-ci-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
go: ['stable']
|
||||
os: [ubuntu-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v7
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
||||
- name: Test
|
||||
- name: Verify module and test source
|
||||
shell: bash
|
||||
run: |
|
||||
# Run all tests without generating a combined coverage profile
|
||||
set -euo pipefail
|
||||
go test ./...
|
||||
|
||||
- name: Create Tag
|
||||
if: success()
|
||||
run: |
|
||||
git config --global user.name 'github-actions'
|
||||
git config --global user.email 'github-actions@github.com'
|
||||
TAG="v0.0.11-$(date +'%Y%m%d%H%M%S')"
|
||||
git tag $TAG
|
||||
git push origin $TAG
|
||||
echo "TAG=$TAG" >> $GITHUB_ENV
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Update README.md
|
||||
if: success()
|
||||
run: |
|
||||
sed -i "s|go get github.com/oneclickvirt/backtrace@.*|go get github.com/oneclickvirt/backtrace@${TAG}|" README.md
|
||||
env:
|
||||
TAG: ${{ env.TAG }}
|
||||
|
||||
- name: Commit and Push README.md
|
||||
if: success()
|
||||
run: |
|
||||
git add README.md
|
||||
git commit -m "Update README.md with new tag ${TAG}"
|
||||
git push origin main
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
go mod verify
|
||||
go test -race -count=1 ./...
|
||||
go vet ./...
|
||||
go build -o "$RUNNER_TEMP/backtrace" ./cmd
|
||||
git diff --check
|
||||
|
||||
243
.github/workflows/main.yaml
vendored
243
.github/workflows/main.yaml
vendored
@ -1,139 +1,128 @@
|
||||
name: Build and Release
|
||||
|
||||
on:
|
||||
# push:
|
||||
# branches: [ main ]
|
||||
# pull_request:
|
||||
# branches: [ main ]
|
||||
# release:
|
||||
# types: [published]
|
||||
push:
|
||||
tags: ["v*"]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_tag:
|
||||
description: Existing semantic version tag to build and release
|
||||
required: true
|
||||
type: string
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
verify:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
tag: ${{ steps.release.outputs.tag }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.release_tag || github.ref }}
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
- id: release
|
||||
name: Verify release tag and source version
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
|
||||
tag="${{ inputs.release_tag }}"
|
||||
else
|
||||
tag="${GITHUB_REF#refs/tags/}"
|
||||
fi
|
||||
[[ "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]
|
||||
git fetch --force --tags origin
|
||||
[[ "$(git rev-list -n 1 "$tag")" == "$(git rev-parse HEAD)" ]]
|
||||
version="$(sed -n 's/^const BackTraceVersion = "\(v[0-9.]*\)"$/\1/p' model/model.go)"
|
||||
[[ "$version" == "$tag" ]]
|
||||
echo "tag=$tag" >> "$GITHUB_OUTPUT"
|
||||
- uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
- name: Validate release source
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
go mod verify
|
||||
go test -race -count=1 ./...
|
||||
go vet ./...
|
||||
go build -o "$RUNNER_TEMP/backtrace" ./cmd
|
||||
|
||||
release:
|
||||
needs: verify
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Create GitHub release
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
RELEASE_TAG: ${{ needs.verify.outputs.tag }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1 || \
|
||||
gh release create "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --verify-tag \
|
||||
--title "$RELEASE_TAG" --generate-notes
|
||||
|
||||
build:
|
||||
name: Build and Test
|
||||
needs: [verify, release]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
||||
- name: Build and Test on Default Platform
|
||||
run: |
|
||||
go build -v ./...
|
||||
go test -v ./...
|
||||
|
||||
- name: Delete Existing Release Assets
|
||||
run: |
|
||||
release_id=$(curl -s -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" "https://api.github.com/repos/oneclickvirt/backtrace/releases/tags/output" | jq -r '.id')
|
||||
echo "Deleting existing release assets..."
|
||||
assets=$(curl -s -H "Accept: application/vnd.github.v3+json" "https://api.github.com/repos/oneclickvirt/backtrace/releases/$release_id/assets" | jq -r '.[] | .id')
|
||||
for asset in $assets; do
|
||||
echo "Deleting asset with ID: $asset"
|
||||
curl -X DELETE -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" "https://api.github.com/repos/oneclickvirt/backtrace/releases/assets/$asset"
|
||||
done
|
||||
sleep 30
|
||||
|
||||
release-binary:
|
||||
name: Release Go Binary
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
||||
- name: Build and Release
|
||||
run: |
|
||||
mkdir -p bin
|
||||
cd cmd
|
||||
CGO_ENABLED=0 GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} GOARM=${{ matrix.goarm }} go build \
|
||||
-o ../bin/backtrace-${{ matrix.goos }}-${{ matrix.goarch }} \
|
||||
-v \
|
||||
-ldflags="-extldflags=-static -s -w" \
|
||||
-trimpath \
|
||||
.
|
||||
|
||||
- name: Upload New Assets
|
||||
run: |
|
||||
release_id=$(curl -s -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" "https://api.github.com/repos/oneclickvirt/backtrace/releases/tags/output" | jq -r '.id')
|
||||
echo "Uploading new assets to release..."
|
||||
for file in ./bin/*; do
|
||||
echo "Uploading $file to release..."
|
||||
curl -s -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
|
||||
-H "Content-Type: application/octet-stream" \
|
||||
--data-binary @"$file" \
|
||||
"https://uploads.github.com/repos/oneclickvirt/backtrace/releases/$release_id/assets?name=$(basename "$file")"
|
||||
rm -rf $file
|
||||
done
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
goos: [windows, freebsd, openbsd, linux, darwin]
|
||||
goarch: [amd64, 386]
|
||||
exclude:
|
||||
- goarch: 386
|
||||
goos: darwin
|
||||
include:
|
||||
- goos: windows
|
||||
goarch: 386
|
||||
- goos: windows
|
||||
goarch: amd64
|
||||
- goos: windows
|
||||
goarch: arm64
|
||||
- goos: darwin
|
||||
goarch: arm64
|
||||
- goos: linux
|
||||
goarch: arm
|
||||
goarm: 7
|
||||
- goos: linux
|
||||
goarch: arm64
|
||||
- goos: linux
|
||||
goarch: riscv64
|
||||
- goos: linux
|
||||
goarch: mips64
|
||||
- goos: linux
|
||||
goarch: mips64le
|
||||
- goos: linux
|
||||
goarch: mipsle
|
||||
- goos: linux
|
||||
goarch: mips
|
||||
- goos: linux
|
||||
goarch: ppc64
|
||||
- goos: linux
|
||||
goarch: ppc64le
|
||||
- goos: freebsd
|
||||
goarch: arm64
|
||||
- goos: freebsd
|
||||
goarch: arm
|
||||
goarm: 7
|
||||
- goos: openbsd
|
||||
goarch: arm64
|
||||
- goos: openbsd
|
||||
goarch: arm
|
||||
goarm: 7
|
||||
# - goos: linux
|
||||
# goarch: mipsle
|
||||
# gomips: softfloat
|
||||
# - goos: linux
|
||||
# goarch: mips
|
||||
# gomips: softfloat
|
||||
# - goos: linux
|
||||
# goarch: arm
|
||||
# goarm: 6
|
||||
# - goos: linux
|
||||
# goarch: arm
|
||||
# goarm: 5
|
||||
- {goos: linux, goarch: amd64}
|
||||
- {goos: linux, goarch: "386"}
|
||||
- {goos: linux, goarch: arm, goarm: "7"}
|
||||
- {goos: linux, goarch: arm64}
|
||||
- {goos: linux, goarch: riscv64}
|
||||
- {goos: linux, goarch: mips}
|
||||
- {goos: linux, goarch: mipsle}
|
||||
- {goos: linux, goarch: mips64}
|
||||
- {goos: linux, goarch: mips64le}
|
||||
- {goos: linux, goarch: ppc64}
|
||||
- {goos: linux, goarch: ppc64le}
|
||||
- {goos: freebsd, goarch: amd64}
|
||||
- {goos: freebsd, goarch: "386"}
|
||||
- {goos: freebsd, goarch: arm, goarm: "7"}
|
||||
- {goos: freebsd, goarch: arm64}
|
||||
- {goos: openbsd, goarch: amd64}
|
||||
- {goos: openbsd, goarch: "386"}
|
||||
- {goos: openbsd, goarch: arm, goarm: "7"}
|
||||
- {goos: openbsd, goarch: arm64}
|
||||
- {goos: darwin, goarch: amd64}
|
||||
- {goos: darwin, goarch: arm64}
|
||||
- {goos: windows, goarch: "386"}
|
||||
- {goos: windows, goarch: amd64}
|
||||
- {goos: windows, goarch: arm64}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.release_tag || github.ref }}
|
||||
- uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
- name: Build binary
|
||||
env:
|
||||
GOOS: ${{ matrix.goos }}
|
||||
GOARCH: ${{ matrix.goarch }}
|
||||
GOARM: ${{ matrix.goarm }}
|
||||
CGO_ENABLED: "0"
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
suffix=""
|
||||
[[ "$GOOS" == "windows" ]] && suffix=".exe"
|
||||
artifact="backtrace-${GOOS}-${GOARCH}${suffix}"
|
||||
go build -trimpath -buildvcs=false -ldflags="-s -w" -o "$artifact" ./cmd
|
||||
echo "ARTIFACT=$artifact" >> "$GITHUB_ENV"
|
||||
- name: Upload release asset
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
RELEASE_TAG: ${{ needs.verify.outputs.tag }}
|
||||
run: gh release upload "$RELEASE_TAG" "$ARTIFACT" --repo "$GITHUB_REPOSITORY" --clobber
|
||||
|
||||
78
.github/workflows/release.yaml
vendored
Normal file
78
.github/workflows/release.yaml
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
name: Prepare and Tag Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_tag:
|
||||
description: Semantic version tag; leave empty to use BackTraceVersion
|
||||
required: false
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: backtrace-release-data
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
ref: main
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
- uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
- id: release
|
||||
name: Resolve release tag
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
version="$(sed -n 's/^const BackTraceVersion = "\(v[0-9.]*\)"$/\1/p' model/model.go)"
|
||||
tag="${{ inputs.release_tag }}"
|
||||
[[ -n "$tag" ]] || tag="$version"
|
||||
[[ "$tag" == "$version" ]]
|
||||
[[ "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]
|
||||
git fetch --force --tags origin
|
||||
! git rev-parse --verify --quiet "refs/tags/$tag"
|
||||
echo "tag=$tag" >> "$GITHUB_OUTPUT"
|
||||
- name: Refresh embedded routing datasets
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
go run ./cmd/update-prefixes -output-dir bk/prefix
|
||||
go run ./cmd/update-asn-metadata
|
||||
- name: Validate release source
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
go mod verify
|
||||
go test -race -count=1 ./...
|
||||
go vet ./...
|
||||
go build -o "$RUNNER_TEMP/backtrace" ./cmd
|
||||
git diff --check
|
||||
- name: Commit refreshed routing datasets
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -z "$(git status --porcelain -- bk/prefix bgptools/data)" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git add bk/prefix bgptools/data
|
||||
git commit -m "chore(data): refresh routing datasets"
|
||||
git push origin HEAD:main
|
||||
- name: Create immutable release tag
|
||||
env:
|
||||
RELEASE_TAG: ${{ steps.release.outputs.tag }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git tag -a "$RELEASE_TAG" -m "Release $RELEASE_TAG"
|
||||
git push origin "refs/tags/$RELEASE_TAG"
|
||||
11
README.md
11
README.md
@ -19,6 +19,7 @@
|
||||
- [x] 多次并发路由检测以分析平均多次路由,避免单次路由因网络波动未能准确检测
|
||||
- [x] 增加对全平台的编译支持,原版[backtrace](https://github.com/zhanghanyun/backtrace)仅支持linux平台的amd64和arm64架构
|
||||
- [x] 兼容额外的ICMP地址获取,若当前目标IP无法查询路由尝试额外的IP地址
|
||||
- [x] 在线但本地 DNS 确认缺失时,在当前进程内使用经过固定地址 TLS 查询校验的最快 DoH/DoT 上游;瞬时超时、SERVFAIL 或丢包不会切换系统解析
|
||||
|
||||
相关输出和查询结果的说明:[跳转](https://github.com/oneclickvirt/ecs/blob/master/README_NEW_USER.md#%E4%B8%8A%E6%B8%B8%E5%8F%8A%E5%9B%9E%E7%A8%8B%E7%BA%BF%E8%B7%AF%E6%A3%80%E6%B5%8B)
|
||||
|
||||
@ -50,7 +51,7 @@ backtrace
|
||||
|
||||
进行测试
|
||||
|
||||
无环境依赖,理论上适配所有系统和主流架构,更多架构请查看 https://github.com/oneclickvirt/backtrace/releases/tag/output
|
||||
无环境依赖,理论上适配所有系统和主流架构,更多架构请查看 https://github.com/oneclickvirt/backtrace/releases
|
||||
|
||||
```
|
||||
Usage: backtrace [options]
|
||||
@ -59,6 +60,8 @@ Usage: backtrace [options]
|
||||
Specify IP address for bgptools
|
||||
-ipv6
|
||||
Enable ipv6 testing
|
||||
-dns-mode string
|
||||
DNS mode (auto, system, doh, or dot) (default "auto")
|
||||
-log
|
||||
Enable logging
|
||||
-s Disabe show ip info (default true)
|
||||
@ -75,9 +78,13 @@ rm -rf /usr/bin/backtrace
|
||||
## 在Golang中使用
|
||||
|
||||
```
|
||||
go get github.com/oneclickvirt/backtrace@v0.0.10-20260702203359
|
||||
go get github.com/oneclickvirt/backtrace@v0.0.21
|
||||
```
|
||||
|
||||
## DNS 解析回退
|
||||
|
||||
`-dns-mode=auto` 是默认值。它会先保留系统 DNS,只有独立探测都确认本地解析器不可用时,才在本次进程内启用内置加密 DNS,并选择实测延迟最低的 DoH 或 DoT 上游。正常响应、NXDOMAIN、短暂超时、SERVFAIL 和丢包都不会被误判为 DNS 缺失。`system` 禁用回退,`doh` 和 `dot` 可显式强制相应协议;不会修改 `/etc/resolv.conf`、`/etc/hosts` 或其他系统配置。
|
||||
|
||||
## 概览图
|
||||
|
||||

|
||||
|
||||
@ -34,13 +34,16 @@ case $os in
|
||||
Linux)
|
||||
case $arch in
|
||||
"x86_64" | "x86" | "amd64" | "x64")
|
||||
wget -O backtrace "${cdn_success_url}https://github.com/oneclickvirt/backtrace/releases/download/output/backtrace-linux-amd64"
|
||||
wget -O backtrace "${cdn_success_url}https://github.com/oneclickvirt/backtrace/releases/latest/download/backtrace-linux-amd64"
|
||||
;;
|
||||
"i386" | "i686")
|
||||
wget -O backtrace "${cdn_success_url}https://github.com/oneclickvirt/backtrace/releases/download/output/backtrace-linux-386"
|
||||
wget -O backtrace "${cdn_success_url}https://github.com/oneclickvirt/backtrace/releases/latest/download/backtrace-linux-386"
|
||||
;;
|
||||
"armv7l" | "armv8" | "armv8l" | "aarch64" | "arm64")
|
||||
wget -O backtrace "${cdn_success_url}https://github.com/oneclickvirt/backtrace/releases/download/output/backtrace-linux-arm64"
|
||||
"armv7l" | "armv8" | "armv8l")
|
||||
wget -O backtrace "${cdn_success_url}https://github.com/oneclickvirt/backtrace/releases/latest/download/backtrace-linux-arm"
|
||||
;;
|
||||
"aarch64" | "arm64")
|
||||
wget -O backtrace "${cdn_success_url}https://github.com/oneclickvirt/backtrace/releases/latest/download/backtrace-linux-arm64"
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported architecture: $arch"
|
||||
@ -51,13 +54,13 @@ case $os in
|
||||
Darwin)
|
||||
case $arch in
|
||||
"x86_64" | "x86" | "amd64" | "x64")
|
||||
wget -O backtrace "${cdn_success_url}https://github.com/oneclickvirt/backtrace/releases/download/output/backtrace-darwin-amd64"
|
||||
wget -O backtrace "${cdn_success_url}https://github.com/oneclickvirt/backtrace/releases/latest/download/backtrace-darwin-amd64"
|
||||
;;
|
||||
"i386" | "i686")
|
||||
wget -O backtrace "${cdn_success_url}https://github.com/oneclickvirt/backtrace/releases/download/output/backtrace-darwin-386"
|
||||
wget -O backtrace "${cdn_success_url}https://github.com/oneclickvirt/backtrace/releases/latest/download/backtrace-darwin-386"
|
||||
;;
|
||||
"armv7l" | "armv8" | "armv8l" | "aarch64" | "arm64")
|
||||
wget -O backtrace "${cdn_success_url}https://github.com/oneclickvirt/backtrace/releases/download/output/backtrace-darwin-arm64"
|
||||
"aarch64" | "arm64")
|
||||
wget -O backtrace "${cdn_success_url}https://github.com/oneclickvirt/backtrace/releases/latest/download/backtrace-darwin-arm64"
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported architecture: $arch"
|
||||
@ -68,13 +71,16 @@ case $os in
|
||||
FreeBSD)
|
||||
case $arch in
|
||||
amd64)
|
||||
wget -O backtrace "${cdn_success_url}https://github.com/oneclickvirt/backtrace/releases/download/output/backtrace-freebsd-amd64"
|
||||
wget -O backtrace "${cdn_success_url}https://github.com/oneclickvirt/backtrace/releases/latest/download/backtrace-freebsd-amd64"
|
||||
;;
|
||||
"i386" | "i686")
|
||||
wget -O backtrace "${cdn_success_url}https://github.com/oneclickvirt/backtrace/releases/download/output/backtrace-freebsd-386"
|
||||
wget -O backtrace "${cdn_success_url}https://github.com/oneclickvirt/backtrace/releases/latest/download/backtrace-freebsd-386"
|
||||
;;
|
||||
"armv7l" | "armv8" | "armv8l" | "aarch64" | "arm64")
|
||||
wget -O backtrace "${cdn_success_url}https://github.com/oneclickvirt/backtrace/releases/download/output/backtrace-freebsd-arm64"
|
||||
"armv7l" | "armv8" | "armv8l")
|
||||
wget -O backtrace "${cdn_success_url}https://github.com/oneclickvirt/backtrace/releases/latest/download/backtrace-freebsd-arm"
|
||||
;;
|
||||
"aarch64" | "arm64")
|
||||
wget -O backtrace "${cdn_success_url}https://github.com/oneclickvirt/backtrace/releases/latest/download/backtrace-freebsd-arm64"
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported architecture: $arch"
|
||||
@ -85,13 +91,16 @@ case $os in
|
||||
OpenBSD)
|
||||
case $arch in
|
||||
amd64)
|
||||
wget -O backtrace "${cdn_success_url}https://github.com/oneclickvirt/backtrace/releases/download/output/backtrace-openbsd-amd64"
|
||||
wget -O backtrace "${cdn_success_url}https://github.com/oneclickvirt/backtrace/releases/latest/download/backtrace-openbsd-amd64"
|
||||
;;
|
||||
"i386" | "i686")
|
||||
wget -O backtrace "${cdn_success_url}https://github.com/oneclickvirt/backtrace/releases/download/output/backtrace-openbsd-386"
|
||||
wget -O backtrace "${cdn_success_url}https://github.com/oneclickvirt/backtrace/releases/latest/download/backtrace-openbsd-386"
|
||||
;;
|
||||
"armv7l" | "armv8" | "armv8l" | "aarch64" | "arm64")
|
||||
wget -O backtrace "${cdn_success_url}https://github.com/oneclickvirt/backtrace/releases/download/output/backtrace-openbsd-arm64"
|
||||
"armv7l" | "armv8" | "armv8l")
|
||||
wget -O backtrace "${cdn_success_url}https://github.com/oneclickvirt/backtrace/releases/latest/download/backtrace-openbsd-arm"
|
||||
;;
|
||||
"aarch64" | "arm64")
|
||||
wget -O backtrace "${cdn_success_url}https://github.com/oneclickvirt/backtrace/releases/latest/download/backtrace-openbsd-arm64"
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported architecture: $arch"
|
||||
|
||||
@ -381,6 +381,7 @@
|
||||
2409:804c:18::/48
|
||||
2409:804c:19::/48
|
||||
2409:804c:2000::/40
|
||||
2409:804c:2000::/48
|
||||
2409:804c:22::/48
|
||||
2409:804c:24::/48
|
||||
2409:804c:27::/48
|
||||
@ -555,6 +556,7 @@
|
||||
2409:805a:2b02::/48
|
||||
2409:805a:2b05::/48
|
||||
2409:805a:2b08::/48
|
||||
2409:805a:2b0b::/48
|
||||
2409:805a:2c01::/48
|
||||
2409:805b:2807::/48
|
||||
2409:805b:280e::/48
|
||||
@ -688,6 +690,8 @@
|
||||
2409:8062:1015::/48
|
||||
2409:8062:1800::/40
|
||||
2409:8062:2000::/40
|
||||
2409:8062:2001::/48
|
||||
2409:8062:2002::/48
|
||||
2409:8062:2800::/40
|
||||
2409:8062:2900::/40
|
||||
2409:8062:2b00::/40
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"schema": "backtrace.asn-prefixes/v1",
|
||||
"file": "as9808.txt",
|
||||
"count": 7166,
|
||||
"sha256": "90cc93ec88ebc7e4f97bfb9eadc2032e9c9ca803e618a2aa811af6f7d0eac1f7",
|
||||
"generated_at": "2026-08-22T01:20:00Z"
|
||||
"count": 7170,
|
||||
"sha256": "b74a4ff30b2f92e153643684758ba892efc426c3f8c6ad355581288fbec71bee",
|
||||
"generated_at": "2026-08-26T07:30:15Z"
|
||||
}
|
||||
|
||||
73
cmd/main.go
73
cmd/main.go
@ -9,6 +9,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@ -16,6 +17,7 @@ import (
|
||||
backtrace "github.com/oneclickvirt/backtrace/bk"
|
||||
"github.com/oneclickvirt/backtrace/model"
|
||||
"github.com/oneclickvirt/backtrace/utils"
|
||||
"github.com/oneclickvirt/basics/network/resolver"
|
||||
. "github.com/oneclickvirt/defaultset"
|
||||
)
|
||||
|
||||
@ -44,9 +46,16 @@ type cliOptions struct {
|
||||
deep bool
|
||||
routeTries int
|
||||
specifiedIP string
|
||||
dnsMode string
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
var (
|
||||
backtraceDNSConfigureFn = resolver.Configure
|
||||
backtraceDNSShutdownFn = resolver.Shutdown
|
||||
backtraceDNSBootstrapReachableFn = resolver.BootstrapReachable
|
||||
)
|
||||
|
||||
func newBacktraceFlagSet(options *cliOptions) *flag.FlagSet {
|
||||
set := flag.NewFlagSet("backtrace", flag.ContinueOnError)
|
||||
set.BoolVar(&options.help, "h", false, "Show help information")
|
||||
@ -61,6 +70,7 @@ func newBacktraceFlagSet(options *cliOptions) *flag.FlagSet {
|
||||
set.BoolVar(&options.deep, "deep", false, "Fetch geofeed and enable WHOIS fallback")
|
||||
set.IntVar(&options.routeTries, "route-attempts", 3, "Traceroute attempts per target (1-5)")
|
||||
set.DurationVar(&options.timeout, "timeout", 15*time.Second, "Structured or route report timeout")
|
||||
set.StringVar(&options.dnsMode, "dns-mode", "auto", "DNS mode (auto, system, doh, or dot)")
|
||||
return set
|
||||
}
|
||||
|
||||
@ -75,13 +85,40 @@ func safeGo(wg *sync.WaitGroup, fn func()) {
|
||||
}()
|
||||
}
|
||||
|
||||
func main() {
|
||||
go func() {
|
||||
resp, err := http.Get("https://hits.spiritlhl.net/backtrace.svg?action=hit&title=Hits&title_bg=%23555555&count_bg=%230eecf8&edge_flat=false")
|
||||
if err == nil && resp != nil && resp.Body != nil {
|
||||
resp.Body.Close()
|
||||
func configureBacktraceResolver(ctx context.Context, mode resolver.Mode, preCheck *utils.NetCheckResult) resolver.Status {
|
||||
requested := resolver.ParseMode(string(mode))
|
||||
config := resolver.Config{Mode: requested}
|
||||
status := resolver.Status{Requested: requested, Active: resolver.ModeUnavailable, Reason: "network unavailable"}
|
||||
if preCheck == nil || preCheck.Connected || requested == resolver.ModeDoH || requested == resolver.ModeDoT {
|
||||
return backtraceDNSConfigureFn(ctx, config)
|
||||
}
|
||||
if requested == resolver.ModeAuto {
|
||||
if _, reachable := backtraceDNSBootstrapReachableFn(ctx, config); reachable {
|
||||
return backtraceDNSConfigureFn(ctx, config)
|
||||
}
|
||||
}()
|
||||
status.Reason = "encrypted DNS endpoint unreachable"
|
||||
}
|
||||
backtraceDNSShutdownFn()
|
||||
return status
|
||||
}
|
||||
|
||||
func promoteEncryptedDNSConnectivity(preCheck *utils.NetCheckResult, status resolver.Status) {
|
||||
if preCheck == nil || preCheck.Connected || (status.Active != resolver.ModeDoH && status.Active != resolver.ModeDoT) {
|
||||
return
|
||||
}
|
||||
preCheck.Connected = true
|
||||
switch status.Stack {
|
||||
case "IPv4":
|
||||
preCheck.HasIPv4 = true
|
||||
case "IPv6":
|
||||
preCheck.HasIPv6 = true
|
||||
}
|
||||
if status.Stack != "" {
|
||||
preCheck.StackType = status.Stack
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
var options cliOptions
|
||||
backtraceFlag := newBacktraceFlagSet(&options)
|
||||
if err := backtraceFlag.Parse(os.Args[1:]); err != nil {
|
||||
@ -99,10 +136,31 @@ func main() {
|
||||
fmt.Println(model.BackTraceVersion)
|
||||
return
|
||||
}
|
||||
options.dnsMode = strings.ToLower(strings.TrimSpace(options.dnsMode))
|
||||
if options.dnsMode != "auto" && options.dnsMode != "system" && options.dnsMode != "doh" && options.dnsMode != "dot" {
|
||||
fmt.Fprintln(os.Stderr, "dns-mode must be auto, system, doh, or dot")
|
||||
os.Exit(2)
|
||||
}
|
||||
if err := validateStructuredOptions(options); err != nil {
|
||||
fmt.Fprintln(os.Stderr, sanitizeErrorText(err.Error()))
|
||||
os.Exit(2)
|
||||
}
|
||||
var preCheck *utils.NetCheckResult
|
||||
if !options.jsonOutput && !options.routeJSON {
|
||||
check := utils.CheckPublicAccess(3 * time.Second)
|
||||
preCheck = &check
|
||||
}
|
||||
status := configureBacktraceResolver(context.Background(), resolver.ParseMode(options.dnsMode), preCheck)
|
||||
promoteEncryptedDNSConnectivity(preCheck, status)
|
||||
if status.Active == resolver.ModeDoH || status.Active == resolver.ModeDoT {
|
||||
defer backtraceDNSShutdownFn()
|
||||
}
|
||||
go func() {
|
||||
resp, err := http.Get("https://hits.spiritlhl.net/backtrace.svg?action=hit&title=Hits&title_bg=%23555555&count_bg=%230eecf8&edge_flat=false")
|
||||
if err == nil && resp != nil && resp.Body != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
}()
|
||||
if options.jsonOutput {
|
||||
config := bgptools.IPBGPReportConfig{
|
||||
Timeout: options.timeout,
|
||||
@ -146,8 +204,7 @@ func main() {
|
||||
}
|
||||
}
|
||||
}
|
||||
preCheck := utils.CheckPublicAccess(3 * time.Second)
|
||||
if !preCheck.Connected {
|
||||
if preCheck == nil || !preCheck.Connected {
|
||||
fmt.Println(Red("PreCheck IP Type Failed"))
|
||||
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
|
||||
fmt.Println("Press Enter to exit...")
|
||||
|
||||
108
cmd/main_test.go
108
cmd/main_test.go
@ -4,11 +4,14 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/oneclickvirt/backtrace/bgptools"
|
||||
backtrace "github.com/oneclickvirt/backtrace/bk"
|
||||
"github.com/oneclickvirt/backtrace/utils"
|
||||
"github.com/oneclickvirt/basics/network/resolver"
|
||||
)
|
||||
|
||||
func TestBacktraceStructuredFlagParsing(t *testing.T) {
|
||||
@ -27,11 +30,114 @@ func TestBacktraceDefaultsPreserveLegacyMode(t *testing.T) {
|
||||
if err := newBacktraceFlagSet(&options).Parse(nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !options.showIPInfo || options.jsonOutput || options.routeJSON || options.deep || options.ipv6 || options.specifiedIP != "" || options.timeout != 15*time.Second || options.routeTries != 3 {
|
||||
if !options.showIPInfo || options.jsonOutput || options.routeJSON || options.deep || options.ipv6 || options.specifiedIP != "" || options.dnsMode != "auto" || options.timeout != 15*time.Second || options.routeTries != 3 {
|
||||
t.Fatalf("legacy defaults changed: %+v", options)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBacktraceDNSModeParsing(t *testing.T) {
|
||||
var options cliOptions
|
||||
if err := newBacktraceFlagSet(&options).Parse([]string{"-dns-mode", "DoT"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
options.dnsMode = strings.ToLower(strings.TrimSpace(options.dnsMode))
|
||||
if options.dnsMode != "dot" {
|
||||
t.Fatalf("dns mode = %q, want dot", options.dnsMode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigureBacktraceResolverScopesBootstrapFallback(t *testing.T) {
|
||||
originalConfigure := backtraceDNSConfigureFn
|
||||
originalShutdown := backtraceDNSShutdownFn
|
||||
originalBootstrapReachable := backtraceDNSBootstrapReachableFn
|
||||
t.Cleanup(func() {
|
||||
backtraceDNSConfigureFn = originalConfigure
|
||||
backtraceDNSShutdownFn = originalShutdown
|
||||
backtraceDNSBootstrapReachableFn = originalBootstrapReachable
|
||||
})
|
||||
tests := []struct {
|
||||
name string
|
||||
mode resolver.Mode
|
||||
connected bool
|
||||
bootstrapReachable bool
|
||||
configuredStatus resolver.Status
|
||||
wantConfigure int
|
||||
wantBootstrap int
|
||||
wantShutdown int
|
||||
}{
|
||||
{
|
||||
name: "connected auto uses normal resolver path",
|
||||
mode: resolver.ModeAuto,
|
||||
connected: true,
|
||||
configuredStatus: resolver.Status{Requested: resolver.ModeAuto, Active: resolver.ModeSystem, SystemAvailable: true},
|
||||
wantConfigure: 1,
|
||||
},
|
||||
{
|
||||
name: "offline auto stops when bootstrap is unreachable",
|
||||
mode: resolver.ModeAuto,
|
||||
wantBootstrap: 1,
|
||||
wantShutdown: 1,
|
||||
},
|
||||
{
|
||||
name: "offline auto retries after reachable bootstrap",
|
||||
mode: resolver.ModeAuto,
|
||||
bootstrapReachable: true,
|
||||
configuredStatus: resolver.Status{Requested: resolver.ModeAuto, Active: resolver.ModeDoH, DoHAvailable: true, Fallback: true, Stack: "IPv4"},
|
||||
wantConfigure: 1,
|
||||
wantBootstrap: 1,
|
||||
},
|
||||
{
|
||||
name: "forced DoH bypasses bootstrap gate",
|
||||
mode: resolver.ModeDoH,
|
||||
configuredStatus: resolver.Status{Requested: resolver.ModeDoH, Active: resolver.ModeDoH, DoHAvailable: true},
|
||||
wantConfigure: 1,
|
||||
},
|
||||
{
|
||||
name: "system mode never falls back",
|
||||
mode: resolver.ModeSystem,
|
||||
wantShutdown: 1,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
configureCalls := 0
|
||||
bootstrapCalls := 0
|
||||
shutdownCalls := 0
|
||||
backtraceDNSConfigureFn = func(_ context.Context, config resolver.Config) resolver.Status {
|
||||
configureCalls++
|
||||
if config.Mode != test.mode {
|
||||
t.Fatalf("resolver mode = %q, want %q", config.Mode, test.mode)
|
||||
}
|
||||
return test.configuredStatus
|
||||
}
|
||||
backtraceDNSBootstrapReachableFn = func(_ context.Context, config resolver.Config) (string, bool) {
|
||||
bootstrapCalls++
|
||||
if config.Mode != resolver.ModeAuto {
|
||||
t.Fatalf("bootstrap mode = %q, want auto", config.Mode)
|
||||
}
|
||||
return "IPv4", test.bootstrapReachable
|
||||
}
|
||||
backtraceDNSShutdownFn = func() { shutdownCalls++ }
|
||||
|
||||
status := configureBacktraceResolver(context.Background(), test.mode, &utils.NetCheckResult{Connected: test.connected})
|
||||
if configureCalls != test.wantConfigure || bootstrapCalls != test.wantBootstrap || shutdownCalls != test.wantShutdown {
|
||||
t.Fatalf("DNS calls = configure:%d bootstrap:%d shutdown:%d, want configure:%d bootstrap:%d shutdown:%d", configureCalls, bootstrapCalls, shutdownCalls, test.wantConfigure, test.wantBootstrap, test.wantShutdown)
|
||||
}
|
||||
if test.wantConfigure == 0 && status.Active != resolver.ModeUnavailable {
|
||||
t.Fatalf("status = %#v, want unavailable", status)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPromoteEncryptedDNSConnectivity(t *testing.T) {
|
||||
preCheck := &utils.NetCheckResult{StackType: "None"}
|
||||
promoteEncryptedDNSConnectivity(preCheck, resolver.Status{Active: resolver.ModeDoT, Stack: "IPv6"})
|
||||
if !preCheck.Connected || !preCheck.HasIPv6 || preCheck.HasIPv4 || preCheck.StackType != "IPv6" {
|
||||
t.Fatalf("successful encrypted DNS did not promote network state: %#v", preCheck)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBacktraceRouteStructuredFlagParsing(t *testing.T) {
|
||||
var options cliOptions
|
||||
set := newBacktraceFlagSet(&options)
|
||||
|
||||
12
go.mod
12
go.mod
@ -5,9 +5,10 @@ go 1.25.3
|
||||
require (
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/imroc/req/v3 v3.59.0
|
||||
github.com/oneclickvirt/defaultset v0.0.0-20240624051018-30a50859e1b5
|
||||
golang.org/x/net v0.55.0
|
||||
golang.org/x/sys v0.45.0
|
||||
github.com/oneclickvirt/basics v0.0.34
|
||||
github.com/oneclickvirt/defaultset v0.0.2-20240624082446
|
||||
golang.org/x/net v0.57.0
|
||||
golang.org/x/sys v0.47.0
|
||||
)
|
||||
|
||||
require (
|
||||
@ -15,11 +16,12 @@ require (
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/icholy/digest v1.1.0 // indirect
|
||||
github.com/klauspost/compress v1.18.2 // indirect
|
||||
github.com/miekg/dns v1.1.73 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/quic-go/quic-go v0.60.0 // indirect
|
||||
github.com/refraction-networking/utls v1.8.2 // indirect
|
||||
go.uber.org/multierr v1.10.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/crypto v0.51.0 // indirect
|
||||
golang.org/x/text v0.37.0 // indirect
|
||||
golang.org/x/crypto v0.54.0 // indirect
|
||||
golang.org/x/text v0.40.0 // indirect
|
||||
)
|
||||
|
||||
26
go.sum
26
go.sum
@ -15,8 +15,12 @@ github.com/imroc/req/v3 v3.59.0 h1:PqKhJHyBmJYob47LVuTHwRZE00ZO6icbLHe5Zra13jo=
|
||||
github.com/imroc/req/v3 v3.59.0/go.mod h1:cwLwwaE90Pq6ZMmKuHA9FivJbg3avnCH8/RA5lUUs74=
|
||||
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
||||
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/oneclickvirt/defaultset v0.0.0-20240624051018-30a50859e1b5 h1:TUM6XzOB7Z7OxyXi3fwlZY9KfuVbvUBusYiNbSfX208=
|
||||
github.com/oneclickvirt/defaultset v0.0.0-20240624051018-30a50859e1b5/go.mod h1:e9Jt4tf2sbemCtc84/XgKcHy9EZ2jkc5x2sW1NiJS+E=
|
||||
github.com/miekg/dns v1.1.73 h1:uhT8nJxmTrPJYClxVxTCX+CVn6qnzSiybRk72Z6DgrE=
|
||||
github.com/miekg/dns v1.1.73/go.mod h1:RW2Obtfd5NZHvOFe3zYG0W8koWOQtAzyHaLo8vASBuQ=
|
||||
github.com/oneclickvirt/basics v0.0.34 h1:iT2lFMrWcxw0UwnFyw4Gasa2CDp4ERULsckBTFVzSUQ=
|
||||
github.com/oneclickvirt/basics v0.0.34/go.mod h1:AqnE9cDa4ThZRvTq7UlUmLYSZBY6CPBzbUhxGT3JWyU=
|
||||
github.com/oneclickvirt/defaultset v0.0.2-20240624082446 h1:5Pg3mK/u/vQvSz7anu0nxzrNdELi/AcDAU1mMsmPzyc=
|
||||
github.com/oneclickvirt/defaultset v0.0.2-20240624082446/go.mod h1:e9Jt4tf2sbemCtc84/XgKcHy9EZ2jkc5x2sW1NiJS+E=
|
||||
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/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4TzM7MFjy0=
|
||||
@ -39,14 +43,16 @@ go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||
go.uber.org/multierr v1.10.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=
|
||||
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
|
||||
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
|
||||
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
|
||||
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
|
||||
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
|
||||
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
|
||||
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
|
||||
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@ -2,7 +2,7 @@ package model
|
||||
|
||||
import "time"
|
||||
|
||||
const BackTraceVersion = "v0.0.20"
|
||||
const BackTraceVersion = "v0.0.21"
|
||||
|
||||
var EnableLoger = false
|
||||
|
||||
|
||||
155
utils/utils.go
155
utils/utils.go
@ -1,160 +1,13 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
basicsutils "github.com/oneclickvirt/basics/utils"
|
||||
)
|
||||
|
||||
type NetCheckResult struct {
|
||||
HasIPv4 bool
|
||||
HasIPv6 bool
|
||||
Connected bool
|
||||
StackType string // "IPv4", "IPv6", "DualStack", "None"
|
||||
}
|
||||
|
||||
func makeResolver(proto, dnsAddr string) *net.Resolver {
|
||||
return &net.Resolver{
|
||||
PreferGo: true,
|
||||
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
d := net.Dialer{
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
return d.DialContext(ctx, proto, dnsAddr)
|
||||
},
|
||||
}
|
||||
}
|
||||
type NetCheckResult = basicsutils.NetCheckResult
|
||||
|
||||
func CheckPublicAccess(timeout time.Duration) NetCheckResult {
|
||||
if timeout < 2*time.Second {
|
||||
timeout = 2 * time.Second
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
resultChan := make(chan string, 8)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
checks := []struct {
|
||||
Tag string
|
||||
Addr string
|
||||
Kind string // udp4, udp6, http4, http6
|
||||
}{
|
||||
// UDP DNS
|
||||
{"IPv4", "223.5.5.5:53", "udp4"}, // 阿里 DNS
|
||||
{"IPv4", "8.8.8.8:53", "udp4"}, // Google DNS
|
||||
{"IPv6", "[2400:3200::1]:53", "udp6"}, // 阿里 IPv6 DNS
|
||||
{"IPv6", "[2001:4860:4860::8888]:53", "udp6"}, // Google IPv6 DNS
|
||||
// HTTP HEAD
|
||||
{"IPv4", "https://www.baidu.com", "http4"}, // 百度
|
||||
{"IPv4", "https://1.1.1.1", "http4"}, // Cloudflare
|
||||
{"IPv6", "https://[2400:3200::1]", "http6"}, // 阿里 IPv6
|
||||
{"IPv6", "https://[2606:4700::1111]", "http6"}, // Cloudflare IPv6
|
||||
}
|
||||
for _, check := range checks {
|
||||
wg.Add(1)
|
||||
go func(tag, addr, kind string) {
|
||||
defer wg.Done()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
}
|
||||
}()
|
||||
switch kind {
|
||||
case "udp4", "udp6":
|
||||
dialer := &net.Dialer{
|
||||
Timeout: timeout / 4,
|
||||
}
|
||||
conn, err := dialer.DialContext(ctx, kind, addr)
|
||||
if err == nil && conn != nil {
|
||||
conn.Close()
|
||||
select {
|
||||
case resultChan <- tag:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
case "http4", "http6":
|
||||
var resolver *net.Resolver
|
||||
if kind == "http4" {
|
||||
resolver = makeResolver("udp4", "223.5.5.5:53")
|
||||
} else {
|
||||
resolver = makeResolver("udp6", "[2400:3200::1]:53")
|
||||
}
|
||||
dialer := &net.Dialer{
|
||||
Timeout: timeout / 4,
|
||||
Resolver: resolver,
|
||||
}
|
||||
transport := &http.Transport{
|
||||
DialContext: dialer.DialContext,
|
||||
MaxIdleConns: 1,
|
||||
MaxIdleConnsPerHost: 1,
|
||||
IdleConnTimeout: time.Second,
|
||||
TLSHandshakeTimeout: timeout / 4,
|
||||
ResponseHeaderTimeout: timeout / 4,
|
||||
DisableKeepAlives: true,
|
||||
}
|
||||
client := &http.Client{
|
||||
Timeout: timeout / 4,
|
||||
Transport: transport,
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
},
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, "HEAD", addr, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err == nil && resp != nil {
|
||||
if resp.Body != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
if resp.StatusCode < 500 {
|
||||
select {
|
||||
case resultChan <- tag:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}(check.Tag, check.Addr, check.Kind)
|
||||
}
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(resultChan)
|
||||
}()
|
||||
hasV4 := false
|
||||
hasV6 := false
|
||||
for {
|
||||
select {
|
||||
case res, ok := <-resultChan:
|
||||
if !ok {
|
||||
goto result
|
||||
}
|
||||
if res == "IPv4" {
|
||||
hasV4 = true
|
||||
}
|
||||
if res == "IPv6" {
|
||||
hasV6 = true
|
||||
}
|
||||
case <-ctx.Done():
|
||||
goto result
|
||||
}
|
||||
}
|
||||
result:
|
||||
stack := "None"
|
||||
if hasV4 && hasV6 {
|
||||
stack = "DualStack"
|
||||
} else if hasV4 {
|
||||
stack = "IPv4"
|
||||
} else if hasV6 {
|
||||
stack = "IPv6"
|
||||
}
|
||||
return NetCheckResult{
|
||||
HasIPv4: hasV4,
|
||||
HasIPv6: hasV6,
|
||||
Connected: hasV4 || hasV6,
|
||||
StackType: stack,
|
||||
}
|
||||
return basicsutils.CheckPublicAccess(timeout)
|
||||
}
|
||||
@ -2,11 +2,15 @@ package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestCheckPublicAccess(t *testing.T) {
|
||||
if os.Getenv("BACKTRACE_INTEGRATION") != "1" {
|
||||
t.Skip("set BACKTRACE_INTEGRATION=1 to run live public-access checks")
|
||||
}
|
||||
timeout := 3 * time.Second
|
||||
result := CheckPublicAccess(timeout)
|
||||
if result.Connected {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user