Compare commits

..

No commits in common. "main" and "v0.0.4-20240702140722" have entirely different histories.

38 changed files with 547 additions and 4986 deletions

View File

@ -1,48 +0,0 @@
name: 创建IPv6检测的前缀
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * 0'
jobs:
fetch-ipv6-prefixes:
runs-on: ubuntu-latest
steps:
- name: 检出代码
uses: actions/checkout@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: 获取并处理多个ASN的IPv6前缀
run: |
mkdir -p bk/prefix/
for asn in AS4809 AS4134 AS9929 AS4837 AS58807 AS9808 AS58453 AS23764; do
echo "处理 $asn..."
curl -s -A "Mozilla/5.0" \
"https://bgp.he.net/$asn" > "${asn}.html"
grep -oE '[0-9a-f:]+::/[0-9]+' "${asn}.html" | sort -u > tmp_prefixes.txt
{
while read prefix; do
ip_part=$(echo "$prefix" | cut -d/ -f1)
prefix_len=$(echo "$prefix" | cut -d/ -f2)
keep_segments=$((prefix_len / 16))
segments=$(echo "$ip_part" | tr ':' '\n' | grep -v '^$')
kept=$(echo "$segments" | head -n "$keep_segments" | tr '\n' ':' | sed 's/:$//')
echo "$kept"
done < tmp_prefixes.txt
} | sort -u > "bk/prefix/${asn,,}.txt"
rm -f "${asn}.html" tmp_prefixes.txt
done
- name: 提交更新到仓库
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add bk/prefix/*.txt
if git diff --cached --quiet; then
echo "无变更,跳过提交。"
else
git commit -m "chore: 更新多个 ASN 的 IPv6 前缀"
git push
fi

View File

@ -7,48 +7,25 @@ jobs:
test: test:
strategy: strategy:
matrix: matrix:
go: ['1.22.x'] go: [ '1.22.x' ]
os: [ubuntu-latest] os: [ ubuntu-latest ]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v2 uses: actions/setup-go@v2
with: with:
go-version: ${{ matrix.go }} go-version: ${{ matrix.go }}
- name: Test - name: Test
run: | run: go test ./... -coverprofile=coverage.txt
# Run all tests without generating a combined coverage profile
set -euo pipefail
go test ./...
- name: Create Tag - name: Create Tag
if: success() if: success() # 仅在测试成功时运行
run: | run: |
git config --global user.name 'github-actions' git config --global user.name 'github-actions'
git config --global user.email 'github-actions@github.com' git config --global user.email 'github-actions@github.com'
TAG="v0.0.8-$(date +'%Y%m%d%H%M%S')" TAG="v0.0.4-$(date +'%Y%m%d%H%M%S')"
git tag $TAG git tag $TAG
git push origin $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: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -36,7 +36,7 @@ jobs:
echo "Deleting asset with ID: $asset" 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" curl -X DELETE -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" "https://api.github.com/repos/oneclickvirt/backtrace/releases/assets/$asset"
done done
sleep 30 sleep 60
release-binary: release-binary:
name: Release Go Binary name: Release Go Binary
@ -55,12 +55,7 @@ jobs:
run: | run: |
mkdir -p bin mkdir -p bin
cd cmd cd cmd
CGO_ENABLED=0 GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} go build \ CGO_ENABLED=0 GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} go build -o ../bin/backtrace-${{ matrix.goos }}-${{ matrix.goarch }} -v -ldflags="-extldflags=-static" .
-o ../bin/backtrace-${{ matrix.goos }}-${{ matrix.goarch }} \
-v \
-ldflags="-extldflags=-static -s -w" \
-trimpath \
.
- name: Upload New Assets - name: Upload New Assets
run: | run: |

View File

@ -1,26 +1,27 @@
# backtrace # backtrace
[![Hits](https://hits.spiritlhl.net/backtrace.svg?action=hit&title=Hits&title_bg=%23555555&count_bg=%230eecf8&edge_flat=false)](https://hits.spiritlhl.net) [![Downloads](https://ghdownload.spiritlhl.net/oneclickvirt/backtrace?color=01c674)](https://github.com/oneclickvirt/backtrace/releases) [![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Foneclickvirt%2Fbacktrace&count_bg=%2323E01C&title_bg=%23555555&icon=sonarcloud.svg&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com) [![Build and Release](https://github.com/oneclickvirt/backtrace/actions/workflows/main.yaml/badge.svg)](https://github.com/oneclickvirt/backtrace/actions/workflows/main.yaml)
[![Build and Release](https://github.com/oneclickvirt/backtrace/actions/workflows/main.yaml/badge.svg)](https://github.com/oneclickvirt/backtrace/actions/workflows/main.yaml)
三网回程路由线路测试 三网回程路由线路测试
基于 https://github.com/zhanghanyun/backtrace 的重构和优化,与原版存在很大不同
路由的线路判断最终还是得人工判断的才准确,本项目测试结果仅供参考 路由的线路判断最终还是得人工判断的才准确,本项目测试结果仅供参考
## 功能 ## 功能
- [x] 检测回程显示IPV4/IPV6地址时的线路(使用1500字节的包)不显示IP地址时显示ASN检测不到 - [x] 检测回程显示IPV4地址时的线路不显示IP地址时显示ASN检测不到原版[backtrace](https://github.com/zhanghanyun/backtrace)也支持
- [x] 支持对```9929```、```4837```和```163```线路的判断 - [x] 支持对```4837```、```9929```和```163```线路的判断,原版[backtrace](https://github.com/zhanghanyun/backtrace)也支持
- [x] 支持对```CTGNET```、```CN2GIA```和```CN2GT```线路的判断 - [x] 支持对```CN2GT```和```CN2GIA```线路的判断,原版[backtrace](https://github.com/zhanghanyun/backtrace)不支持,原版全部识别为```CN2```了
- [x] 支持对```CMIN2```和```CMI```线路的判断 - [x] 支持对```CMIN2```和```CMI```线路的判断,原版[backtrace](https://github.com/zhanghanyun/backtrace)也支持
- [x] 支持对整个回程路由进行线路分析一个目标IP可能会分析出多种线路 - [x] 支持对整个回程路由进行线路分析,与原版[backtrace](https://github.com/zhanghanyun/backtrace)仅进行一次判断不同
- [x] 支持对主流接入点的线路检测,方便分析国际互联能力 - [x] 修复原版[backtrace](https://github.com/zhanghanyun/backtrace)对IPV4地址信息获取时json解析失败依然打印信息的问题本项目忽略错误继续执行路由线路查询
- [x] 多次并发路由检测以分析平均多次路由,避免单次路由因网络波动未能准确检测
- [x] 增加对全平台的编译支持,原版[backtrace](https://github.com/zhanghanyun/backtrace)仅支持linux平台的amd64和arm64架构 - [x] 增加对全平台的编译支持,原版[backtrace](https://github.com/zhanghanyun/backtrace)仅支持linux平台的amd64和arm64架构
- [x] 兼容额外的ICMP地址获取若当前目标IP无法查询路由尝试额外的IP地址
相关输出和查询结果的说明:[跳转](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) ## TODO
- [ ] 使用nexttrace进行路由检测备用方案才使用本地路由检测
- [ ] 自动检测汇聚层,裁剪结果不输出汇聚层后的线路
## 使用 ## 使用
@ -54,13 +55,8 @@ backtrace
``` ```
Usage: backtrace [options] Usage: backtrace [options]
-e Enable logging
-h Show help information -h Show help information
-ip string
Specify IP address for bgptools
-ipv6
Enable ipv6 testing
-log
Enable logging
-s Disabe show ip info (default true) -s Disabe show ip info (default true)
-v Show version -v Show version
``` ```
@ -75,7 +71,7 @@ rm -rf /usr/bin/backtrace
## 在Golang中使用 ## 在Golang中使用
``` ```
go get github.com/oneclickvirt/backtrace@v0.0.8-20251109090457 go get github.com/oneclickvirt/backtrace@latest
``` ```
## 概览图 ## 概览图
@ -85,9 +81,3 @@ go get github.com/oneclickvirt/backtrace@v0.0.8-20251109090457
![图片](https://github.com/oneclickvirt/backtrace/assets/103393591/2812a47d-4e6b-4091-9bb9-596af6c3c8bc) ![图片](https://github.com/oneclickvirt/backtrace/assets/103393591/2812a47d-4e6b-4091-9bb9-596af6c3c8bc)
![图片](https://github.com/oneclickvirt/backtrace/assets/103393591/2e5cc625-e0da-41ff-85ff-9d21c01114a3) ![图片](https://github.com/oneclickvirt/backtrace/assets/103393591/2e5cc625-e0da-41ff-85ff-9d21c01114a3)
## Thanks
部分代码基于 https://github.com/zhanghanyun/backtrace 的重构和优化,与原版存在很大不同
IPV4/IPV6可ICMP进行ping测试的 https://github.com/spiritLHLS/icmp_targets 收集仓库

View File

@ -1,4 +0,0 @@
# 部分参考资料
https://blog.sunflyer.cn/archives/594

View File

@ -1,403 +0,0 @@
package bgptools
import (
"fmt"
"html"
"io"
"net"
"regexp"
"strings"
"time"
"github.com/google/uuid"
"github.com/imroc/req/v3"
"github.com/oneclickvirt/backtrace/model"
"github.com/oneclickvirt/defaultset"
)
type ASCard struct {
ASN string
Name string
Fill string
Stroke string
ID string
}
type Arrow struct {
From string
To string
}
type Upstream struct {
ASN string
Name string
Direct bool
Tier1 bool
Type string
}
type PoPResult struct {
TargetASN string
Upstreams []Upstream
Result string
}
type retryConfig struct {
maxRetries int
timeouts []time.Duration
}
var defaultRetryConfig = retryConfig{
maxRetries: 2,
timeouts: []time.Duration{5 * time.Second, 6 * time.Second},
}
func executeWithRetry(client *req.Client, url string, config retryConfig) (*req.Response, error) {
var lastErr error
for attempt := 0; attempt < config.maxRetries; attempt++ {
timeout := config.timeouts[attempt]
resp, err := client.SetTimeout(timeout).R().
Get(url)
if err == nil && resp.StatusCode == 200 {
return resp, nil
}
if err != nil {
lastErr = fmt.Errorf("attempt %d failed with timeout %v: %w", attempt+1, timeout, err)
} else {
lastErr = fmt.Errorf("attempt %d failed with HTTP status %d (timeout %v)", attempt+1, resp.StatusCode, timeout)
}
if attempt < config.maxRetries-1 {
time.Sleep(1 * time.Second)
}
}
return nil, fmt.Errorf("all %d attempts failed, last error: %w", config.maxRetries, lastErr)
}
func getISPAbbr(asn, name string) string {
if abbr, ok := model.Tier1Global[asn]; ok {
return abbr
}
if idx := strings.Index(name, " "); idx != -1 && idx >= 18 {
return name[:idx]
}
return strings.TrimSpace(name)
}
func getISPType(asn string, tier1 bool, direct bool) string {
switch {
case tier1 && direct && model.Tier1Global[asn] != "":
return "Tier1 Global"
case tier1 && direct && model.Tier1Regional[asn] != "":
return "Tier1 Regional"
case tier1 && !direct:
return "Tier1 Indirect"
case model.Tier2[asn] != "":
return "Tier2"
case model.ContentProviders[asn] != "":
return "CDN Provider"
case model.IXPS[asn] != "":
return "IXP"
case direct:
return "Direct"
default:
return "Indirect"
}
}
func isValidIP(ip string) bool {
return net.ParseIP(ip) != nil
}
func getSVGPath(ip string) (string, error) {
if !isValidIP(ip) {
return "", fmt.Errorf("invalid IP address: %s", ip)
}
var lastErr error
for attempt := 0; attempt < defaultRetryConfig.maxRetries; attempt++ {
client := req.C().ImpersonateChrome()
url := fmt.Sprintf("https://bgp.tools/prefix/%s#connectivity", ip)
resp, err := executeWithRetry(client, url, defaultRetryConfig)
if err == nil {
body := resp.String()
re := regexp.MustCompile(`<img[^>]+id="pathimg"[^>]+src="([^"]+)"`)
matches := re.FindStringSubmatch(body)
if len(matches) >= 2 {
return matches[1], nil
}
lastErr = fmt.Errorf("SVG path not found for IP %s", ip)
} else {
lastErr = fmt.Errorf("failed to fetch BGP info for IP %s: %w", ip, err)
}
if attempt < defaultRetryConfig.maxRetries-1 {
time.Sleep(1 * time.Second)
}
}
return "", fmt.Errorf("failed to get SVG path after %d retries: %w", defaultRetryConfig.maxRetries, lastErr)
}
func downloadSVG(svgPath string) (string, error) {
var lastErr error
for attempt := 0; attempt < defaultRetryConfig.maxRetries; attempt++ {
client := req.C().ImpersonateChrome()
uuid := uuid.NewString()
url := fmt.Sprintf("https://bgp.tools%s?%s&loggedin", svgPath, uuid)
resp, err := executeWithRetry(client, url, defaultRetryConfig)
if err == nil {
bodyBytes, err := io.ReadAll(resp.Body)
if err == nil {
return string(bodyBytes), nil
}
lastErr = fmt.Errorf("failed to read SVG response body: %w", err)
} else {
lastErr = fmt.Errorf("failed to download SVG: %w", err)
}
if attempt < defaultRetryConfig.maxRetries-1 {
time.Sleep(1 * time.Second)
}
}
return "", fmt.Errorf("failed to download SVG after %d retries: %w", defaultRetryConfig.maxRetries, lastErr)
}
func parseASAndEdges(svg string) ([]ASCard, []Arrow) {
svg = html.UnescapeString(svg)
var nodes []ASCard
var edges []Arrow
nodeRE := regexp.MustCompile(`(?s)<g id="node\d+" class="node">(.*?)</g>`)
edgeRE := regexp.MustCompile(`(?s)<g id="edge\d+" class="edge">(.*?)</g>`)
asnRE := regexp.MustCompile(`<title>AS(\d+)</title>`)
nameRE := regexp.MustCompile(`xlink:title="([^"]+)"`)
fillRE := regexp.MustCompile(`<polygon[^>]+fill="([^"]+)"`)
strokeRE := regexp.MustCompile(`<polygon[^>]+stroke="([^"]+)"`)
titleRE := regexp.MustCompile(`<title>AS(\d+)->AS(\d+)</title>`)
for _, match := range nodeRE.FindAllStringSubmatch(svg, -1) {
block := match[1]
asn := ""
if a := asnRE.FindStringSubmatch(block); len(a) > 1 {
asn = a[1]
}
name := "unknown"
if n := nameRE.FindStringSubmatch(block); len(n) > 1 {
name = strings.TrimSpace(n[1])
}
fill := "none"
if f := fillRE.FindStringSubmatch(block); len(f) > 1 {
fill = f[1]
}
stroke := "none"
if s := strokeRE.FindStringSubmatch(block); len(s) > 1 {
stroke = s[1]
}
if asn != "" {
nodes = append(nodes, ASCard{
ASN: asn,
Name: name,
Fill: fill,
Stroke: stroke,
ID: "",
})
}
}
for _, match := range edgeRE.FindAllStringSubmatch(svg, -1) {
block := match[1]
if t := titleRE.FindStringSubmatch(block); len(t) == 3 {
edges = append(edges, Arrow{
From: t[1],
To: t[2],
})
}
}
return nodes, edges
}
func findTargetASN(nodes []ASCard) string {
for _, n := range nodes {
if n.Fill == "limegreen" || n.Stroke == "limegreen" || n.Fill == "green" {
return n.ASN
}
}
if len(nodes) > 0 {
return nodes[0].ASN
}
return ""
}
func findUpstreams(targetASN string, nodes []ASCard, edges []Arrow) []Upstream {
upstreamMap := map[string]bool{}
for _, e := range edges {
if e.From == targetASN {
upstreamMap[e.To] = true
}
}
var upstreams []Upstream
addedASNs := map[string]bool{}
for _, n := range nodes {
if !upstreamMap[n.ASN] {
continue
}
isTier1 := (n.Fill == "white" && n.Stroke == "#005ea5")
upstreamType := getISPType(n.ASN, isTier1, true)
upstreams = append(upstreams, Upstream{
ASN: n.ASN,
Name: n.Name,
Direct: true,
Tier1: isTier1,
Type: upstreamType,
})
addedASNs[n.ASN] = true
}
if len(upstreams) == 1 {
currentASN := upstreams[0].ASN
for {
nextUpstreams := map[string]bool{}
for _, e := range edges {
if e.From == currentASN {
nextUpstreams[e.To] = true
}
}
if len(nextUpstreams) != 1 {
break
}
var nextASN string
for asn := range nextUpstreams {
nextASN = asn
break
}
if addedASNs[nextASN] {
break
}
var nextNode *ASCard
for _, n := range nodes {
if n.ASN == nextASN {
nextNode = &n
break
}
}
if nextNode == nil {
break
}
isTier1 := (nextNode.Fill == "white" && nextNode.Stroke == "#005ea5")
upstreamType := getISPType(nextNode.ASN, isTier1, false)
upstreams = append(upstreams, Upstream{
ASN: nextNode.ASN,
Name: nextNode.Name,
Direct: false,
Tier1: isTier1,
Type: upstreamType,
})
addedASNs[nextNode.ASN] = true
currentASN = nextASN
}
} else if len(upstreams) > 1 {
for _, directUpstream := range upstreams {
currentASN := directUpstream.ASN
for {
nextUpstreams := map[string]bool{}
for _, e := range edges {
if e.From == currentASN {
nextUpstreams[e.To] = true
}
}
if len(nextUpstreams) != 1 {
break
}
var nextASN string
for asn := range nextUpstreams {
nextASN = asn
break
}
if addedASNs[nextASN] {
break
}
var nextNode *ASCard
for _, n := range nodes {
if n.ASN == nextASN {
nextNode = &n
break
}
}
if nextNode == nil {
break
}
isTier1 := (nextNode.Fill == "white" && nextNode.Stroke == "#005ea5")
if isTier1 {
upstreamType := getISPType(nextNode.ASN, isTier1, false)
upstreams = append(upstreams, Upstream{
ASN: nextNode.ASN,
Name: nextNode.Name,
Direct: false,
Tier1: isTier1,
Type: upstreamType,
})
addedASNs[nextNode.ASN] = true
break
}
currentASN = nextASN
}
}
}
return upstreams
}
func GetPoPInfo(ip string) (*PoPResult, error) {
if ip == "" {
return nil, fmt.Errorf("IP address cannot be empty")
}
svgPath, err := getSVGPath(ip)
if err != nil {
return nil, fmt.Errorf("获取SVG路径失败: %w", err)
}
svg, err := downloadSVG(svgPath)
if err != nil {
return nil, fmt.Errorf("下载SVG失败: %w", err)
}
nodes, edges := parseASAndEdges(svg)
if len(nodes) == 0 {
return nil, fmt.Errorf("未找到任何AS节点")
}
targetASN := findTargetASN(nodes)
if targetASN == "" {
return nil, fmt.Errorf("无法识别目标 ASN")
}
upstreams := findUpstreams(targetASN, nodes, edges)
colWidth := 18
center := func(s string) string {
runeLen := len([]rune(s))
if runeLen >= colWidth {
return string([]rune(s)[:colWidth])
}
padding := colWidth - runeLen
left := padding / 2
right := padding - left
return strings.Repeat(" ", left) + s + strings.Repeat(" ", right)
}
var result strings.Builder
perLine := 5
for i := 0; i < len(upstreams); i += perLine {
end := i + perLine
if end > len(upstreams) {
end = len(upstreams)
}
batch := upstreams[i:end]
var line1, line2, line3 []string
for _, u := range batch {
abbr := getISPAbbr(u.ASN, u.Name)
asStr := center("AS" + u.ASN)
abbrStr := center(abbr)
typeStr := center(u.Type)
line1 = append(line1, defaultset.White(asStr))
line2 = append(line2, abbrStr)
line3 = append(line3, defaultset.Blue(typeStr))
}
result.WriteString(strings.Join(line1, ""))
result.WriteString("\n")
result.WriteString(strings.Join(line2, ""))
result.WriteString("\n")
result.WriteString(strings.Join(line3, ""))
result.WriteString("\n")
}
return &PoPResult{
TargetASN: targetASN,
Upstreams: upstreams,
Result: result.String(),
}, nil
}

View File

@ -1,19 +0,0 @@
package bgptools
import (
"fmt"
"testing"
)
func TestGetPoPInfo(t *testing.T) {
result, err := GetPoPInfo("23.128.228.123")
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Printf("目标 ASN: %s\n", result.TargetASN)
fmt.Println(len(result.Upstreams))
fmt.Println(result.Upstreams)
fmt.Println("上游信息:")
fmt.Print(result.Result)
}

155
bk/asn.go Normal file
View File

@ -0,0 +1,155 @@
package backtrace
import (
"fmt"
"net"
"strings"
. "github.com/oneclickvirt/defaultset"
)
type Result struct {
i int
s string
}
var (
ips = []string{
// "219.141.136.12", "202.106.50.1",
"219.141.140.10", "202.106.195.68", "221.179.155.161",
"202.96.209.133", "210.22.97.1", "211.136.112.200",
"58.60.188.222", "210.21.196.6", "120.196.165.24",
"61.139.2.69", "119.6.6.6", "211.137.96.205",
}
names = []string{
"北京电信", "北京联通", "北京移动",
"上海电信", "上海联通", "上海移动",
"广州电信", "广州联通", "广州移动",
"成都电信", "成都联通", "成都移动",
}
m = map[string]string{
// [] 前的字符串个数中文占2个字符串
"AS4809a": "电信CN2GIA [精品线路]",
"AS4809b": "电信CN2GT [优质线路]",
"AS4134": "电信163 [普通线路]",
"AS9929": "联通9929 [优质线路]",
"AS4837": "联通4837 [普通线路]",
"AS58807": "移动CMIN2 [精品线路]",
"AS9808": "移动CMI [普通线路]",
"AS58453": "移动CMI [普通线路]",
}
)
func removeDuplicates(elements []string) []string {
encountered := map[string]bool{} // 用于存储已经遇到的元素
result := []string{} // 存储去重后的结果
for v := range elements { // 遍历切片中的元素
if encountered[elements[v]] == true { // 如果该元素已经遇到过
// 存在过就不加入了
} else {
encountered[elements[v]] = true // 将该元素标记为已经遇到
result = append(result, elements[v]) // 将该元素加入到结果切片中
}
}
return result // 返回去重后的结果切片
}
func trace(ch chan Result, i int) {
hops, err := Trace(net.ParseIP(ips[i]))
if err != nil {
s := fmt.Sprintf("%v %-15s %v", names[i], ips[i], err)
ch <- Result{i, s}
return
}
var asns []string
for _, h := range hops {
for _, n := range h.Nodes {
asn := ipAsn(n.IP.String())
if asn != "" {
asns = append(asns, asn)
}
}
}
// 处理CN2不同路线的区别
if asns != nil && len(asns) > 0 {
var tempText string
asns = removeDuplicates(asns)
tempText += fmt.Sprintf("%v ", names[i])
hasAS4134 := false
hasAS4809 := false
for _, asn := range asns {
if asn == "AS4134" {
hasAS4134 = true
}
if asn == "AS4809" {
hasAS4809 = true
}
}
// 判断是否包含 AS4134 和 AS4809
if hasAS4134 && hasAS4809 {
// 同时包含 AS4134 和 AS4809 属于 CN2GT
asns = append([]string{"AS4809b"}, asns...)
} else if hasAS4809 {
// 仅包含 AS4809 属于 CN2GIA
asns = append([]string{"AS4809a"}, asns...)
}
tempText += fmt.Sprintf("%-15s ", ips[i])
for _, asn := range asns {
asnDescription := m[asn]
switch asn {
case "":
continue
case "AS4809": // 被 AS4809a 和 AS4809b 替代了
continue
case "AS9929":
if !strings.Contains(tempText, asnDescription) {
tempText += DarkGreen(asnDescription) + " "
}
case "AS4809a":
if !strings.Contains(tempText, asnDescription) {
tempText += DarkGreen(asnDescription) + " "
}
case "AS4809b":
if !strings.Contains(tempText, asnDescription) {
tempText += Green(asnDescription) + " "
}
case "AS58807":
if !strings.Contains(tempText, asnDescription) {
tempText += Green(asnDescription) + " "
}
default:
if !strings.Contains(tempText, asnDescription) {
tempText += White(asnDescription) + " "
}
}
}
if tempText == (fmt.Sprintf("%v ", names[i]) + fmt.Sprintf("%-15s ", ips[i])) {
tempText += fmt.Sprintf("%v", Red("检测不到已知线路的ASN"))
}
ch <- Result{i, tempText}
} else {
s := fmt.Sprintf("%v %-15s %v", names[i], ips[i], Red("检测不到回程路由节点的IP地址"))
ch <- Result{i, s}
}
}
func ipAsn(ip string) string {
switch {
case strings.HasPrefix(ip, "59.43"):
return "AS4809"
case strings.HasPrefix(ip, "202.97"):
return "AS4134"
case strings.HasPrefix(ip, "218.105") || strings.HasPrefix(ip, "210.51"):
return "AS9929"
case strings.HasPrefix(ip, "219.158"):
return "AS4837"
case strings.HasPrefix(ip, "223.120.19") || strings.HasPrefix(ip, "223.120.17") || strings.HasPrefix(ip, "223.120.16") ||
strings.HasPrefix(ip, "223.120.140") || strings.HasPrefix(ip, "223.120.130") || strings.HasPrefix(ip, "223.120.131") ||
strings.HasPrefix(ip, "223.120.141"):
return "AS58807"
case strings.HasPrefix(ip, "223.118") || strings.HasPrefix(ip, "223.119") || strings.HasPrefix(ip, "223.120") || strings.HasPrefix(ip, "223.121"):
return "AS58453"
default:
return ""
}
}

View File

@ -1,87 +1,29 @@
package backtrace package backtrace
import ( import (
"strings" "fmt"
"time" "time"
"github.com/oneclickvirt/backtrace/model"
) )
func BackTrace(enableIpv6 bool) string { func BackTrace() {
if model.CachedIcmpData == "" || model.ParsedIcmpTargets == nil || time.Since(model.CachedIcmpDataFetchTime) > time.Hour {
model.CachedIcmpData = getData(model.IcmpTargets)
model.CachedIcmpDataFetchTime = time.Now()
if model.CachedIcmpData != "" {
model.ParsedIcmpTargets = parseIcmpTargets(model.CachedIcmpData)
}
}
var builder strings.Builder
if enableIpv6 {
ipv4Count := len(model.Ipv4s)
ipv6Count := len(model.Ipv6s)
totalCount := ipv4Count + ipv6Count
var ( var (
s = make([]string, totalCount) s [12]string // 对应 ips 目标地址数量
c = make(chan Result) c = make(chan Result)
t = time.After(time.Second * 10) t = time.After(time.Second * 10)
) )
for i := range model.Ipv4s { for i := range ips {
go trace(c, i) go trace(c, i)
} }
for i := range model.Ipv6s { loop:
go traceIPv6(c, i, ipv4Count)
}
loopIPv4v6:
for range s { for range s {
select { select {
case o := <-c: case o := <-c:
s[o.i] = o.s s[o.i] = o.s
case <-t: case <-t:
break loopIPv4v6 break loop
} }
} }
// 收集 IPv4 结果
for i := 0; i < ipv4Count; i++ {
if s[i] != "" {
builder.WriteString(s[i])
builder.WriteString("\n")
}
}
// 收集 IPv6 结果
for i := ipv4Count; i < totalCount; i++ {
if s[i] != "" {
builder.WriteString(s[i])
builder.WriteString("\n")
}
}
} else {
ipCount := len(model.Ipv4s)
var (
s = make([]string, ipCount)
c = make(chan Result)
t = time.After(time.Second * 10)
)
for i := range model.Ipv4s {
go trace(c, i)
}
loopIPv4:
for range s {
select {
case o := <-c:
s[o.i] = o.s
case <-t:
break loopIPv4
}
}
// 收集结果
for _, r := range s { for _, r := range s {
if r != "" { fmt.Println(r)
builder.WriteString(r)
builder.WriteString("\n")
} }
}
}
// 返回完整结果,去掉末尾的换行符
result := builder.String()
return strings.TrimSuffix(result, "\n")
} }

View File

@ -16,5 +16,5 @@ import (
//} //}
func TestBackTrace(t *testing.T) { func TestBackTrace(t *testing.T) {
BackTrace(false) BackTrace()
} }

View File

@ -1,31 +0,0 @@
package backtrace
import (
"strings"
)
func ipv4Asn(ip string) string {
if strings.Contains(ip, ":") {
return ipv6Asn(ip)
}
switch {
case strings.HasPrefix(ip, "59.43"):
return "AS4809"
case strings.HasPrefix(ip, "202.97"):
return "AS4134"
case strings.HasPrefix(ip, "218.105") || strings.HasPrefix(ip, "210.51"):
return "AS9929"
case strings.HasPrefix(ip, "219.158"):
return "AS4837"
case strings.HasPrefix(ip, "223.120.19") || strings.HasPrefix(ip, "223.120.17") || strings.HasPrefix(ip, "223.120.16") ||
strings.HasPrefix(ip, "223.120.140") || strings.HasPrefix(ip, "223.120.130") || strings.HasPrefix(ip, "223.120.131") ||
strings.HasPrefix(ip, "223.120.141"):
return "AS58807"
case strings.HasPrefix(ip, "223.118") || strings.HasPrefix(ip, "223.119") || strings.HasPrefix(ip, "223.120") || strings.HasPrefix(ip, "223.121"):
return "AS58453"
case strings.HasPrefix(ip, "69.194") || strings.HasPrefix(ip, "203.22"):
return "AS23764"
default:
return ""
}
}

View File

@ -1,59 +0,0 @@
package backtrace
import (
"strings"
_ "embed"
)
//go:embed prefix/as4809.txt
var as4809Data string
//go:embed prefix/as4134.txt
var as4134Data string
//go:embed prefix/as9929.txt
var as9929Data string
//go:embed prefix/as4837.txt
var as4837Data string
//go:embed prefix/as58807.txt
var as58807Data string
//go:embed prefix/as9808.txt
var as9808Data string
//go:embed prefix/as58453.txt
var as58453Data string
//go:embed prefix/as23764.txt
var as23764Data string
// ASN -> Prefix strings
var asnPrefixes = map[string][]string{
"AS4809": strings.Split(as4809Data, "\n"), // 电信 CN2 GT/GIA
"AS4134": strings.Split(as4134Data, "\n"), // 电信 163 骨干网
"AS9929": strings.Split(as9929Data, "\n"), // 联通 9929 优质国际线路
"AS4837": strings.Split(as4837Data, "\n"), // 联通 AS4837 普通国际线路
"AS58807": strings.Split(as58807Data, "\n"), // 移动 CMIN2 国际精品网
"AS9808": strings.Split(as9808Data, "\n"), // 移动 CMI中国移动国际公司
"AS58453": strings.Split(as58453Data, "\n"), // 移动国际互联网CMI/HK
"AS23764": strings.Split(as23764Data, "\n"), // 电信 CTGNET/国际出口可能是CN2-B
}
// 判断 IPv6 地址是否匹配 ASN 中的某个前缀
func ipv6Asn(ip string) string {
ip = strings.ToLower(ip)
for asn, prefixes := range asnPrefixes {
for _, prefix := range prefixes {
prefix = strings.TrimSpace(prefix)
if prefix == "" {
continue
}
if strings.HasPrefix(ip, prefix) {
return asn
}
}
}
return ""
}

View File

@ -1,41 +0,0 @@
//go:build linux || freebsd || openbsd || darwin
// +build linux freebsd openbsd darwin
package backtrace
import (
"net"
"syscall"
"github.com/oneclickvirt/backtrace/model"
. "github.com/oneclickvirt/defaultset"
)
func (t *Tracer) listen(network string, laddr *net.IPAddr) (*net.IPConn, error) {
conn, err := net.ListenIP(network, laddr)
if err != nil {
if model.EnableLoger {
Logger.Info(err.Error())
}
return nil, err
}
raw, err := conn.SyscallConn()
if err != nil {
if model.EnableLoger {
Logger.Info(err.Error())
}
conn.Close()
return nil, err
}
_ = raw.Control(func(fd uintptr) {
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_HDRINCL, 1)
})
if err != nil {
if model.EnableLoger {
Logger.Info(err.Error())
}
conn.Close()
return nil, err
}
return conn, nil
}

53
bk/listen_darwin.go Normal file
View File

@ -0,0 +1,53 @@
package backtrace
import (
"net"
"syscall"
. "github.com/oneclickvirt/defaultset"
)
func (t *Tracer) listen(network string, laddr *net.IPAddr) (*net.IPConn, error) {
if EnableLoger {
InitLogger()
defer Logger.Sync()
conn, err := net.ListenIP(network, laddr)
if err != nil {
Logger.Info(err.Error())
return nil, err
}
raw, err := conn.SyscallConn()
if err != nil {
Logger.Info(err.Error())
conn.Close()
return nil, err
}
_ = raw.Control(func(fd uintptr) {
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_HDRINCL, 1)
})
if err != nil {
Logger.Info(err.Error())
conn.Close()
return nil, err
}
return conn, nil
} else {
conn, err := net.ListenIP(network, laddr)
if err != nil {
return nil, err
}
raw, err := conn.SyscallConn()
if err != nil {
conn.Close()
return nil, err
}
_ = raw.Control(func(fd uintptr) {
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_HDRINCL, 1)
})
if err != nil {
conn.Close()
return nil, err
}
return conn, nil
}
}

53
bk/listen_freebsd.go Normal file
View File

@ -0,0 +1,53 @@
package backtrace
import (
"net"
"syscall"
. "github.com/oneclickvirt/defaultset"
)
func (t *Tracer) listen(network string, laddr *net.IPAddr) (*net.IPConn, error) {
if EnableLoger {
InitLogger()
defer Logger.Sync()
conn, err := net.ListenIP(network, laddr)
if err != nil {
Logger.Info(err.Error())
return nil, err
}
raw, err := conn.SyscallConn()
if err != nil {
Logger.Info(err.Error())
conn.Close()
return nil, err
}
_ = raw.Control(func(fd uintptr) {
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_HDRINCL, 1)
})
if err != nil {
Logger.Info(err.Error())
conn.Close()
return nil, err
}
return conn, nil
} else {
conn, err := net.ListenIP(network, laddr)
if err != nil {
return nil, err
}
raw, err := conn.SyscallConn()
if err != nil {
conn.Close()
return nil, err
}
_ = raw.Control(func(fd uintptr) {
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_HDRINCL, 1)
})
if err != nil {
conn.Close()
return nil, err
}
return conn, nil
}
}

53
bk/listen_linux.go Normal file
View File

@ -0,0 +1,53 @@
package backtrace
import (
"net"
"syscall"
. "github.com/oneclickvirt/defaultset"
)
func (t *Tracer) listen(network string, laddr *net.IPAddr) (*net.IPConn, error) {
if EnableLoger {
InitLogger()
defer Logger.Sync()
conn, err := net.ListenIP(network, laddr)
if err != nil {
Logger.Info(err.Error())
return nil, err
}
raw, err := conn.SyscallConn()
if err != nil {
Logger.Info(err.Error())
conn.Close()
return nil, err
}
_ = raw.Control(func(fd uintptr) {
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_HDRINCL, 1)
})
if err != nil {
Logger.Info(err.Error())
conn.Close()
return nil, err
}
return conn, nil
} else {
conn, err := net.ListenIP(network, laddr)
if err != nil {
return nil, err
}
raw, err := conn.SyscallConn()
if err != nil {
conn.Close()
return nil, err
}
_ = raw.Control(func(fd uintptr) {
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_HDRINCL, 1)
})
if err != nil {
conn.Close()
return nil, err
}
return conn, nil
}
}

53
bk/listen_openbsd.go Normal file
View File

@ -0,0 +1,53 @@
package backtrace
import (
"net"
"syscall"
. "github.com/oneclickvirt/defaultset"
)
func (t *Tracer) listen(network string, laddr *net.IPAddr) (*net.IPConn, error) {
if EnableLoger {
InitLogger()
defer Logger.Sync()
conn, err := net.ListenIP(network, laddr)
if err != nil {
Logger.Info(err.Error())
return nil, err
}
raw, err := conn.SyscallConn()
if err != nil {
Logger.Info(err.Error())
conn.Close()
return nil, err
}
_ = raw.Control(func(fd uintptr) {
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_HDRINCL, 1)
})
if err != nil {
Logger.Info(err.Error())
conn.Close()
return nil, err
}
return conn, nil
} else {
conn, err := net.ListenIP(network, laddr)
if err != nil {
return nil, err
}
raw, err := conn.SyscallConn()
if err != nil {
conn.Close()
return nil, err
}
_ = raw.Control(func(fd uintptr) {
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_HDRINCL, 1)
})
if err != nil {
conn.Close()
return nil, err
}
return conn, nil
}
}

View File

@ -1,45 +0,0 @@
//go:build !linux && !freebsd && !openbsd && !darwin && !windows
// +build !linux,!freebsd,!openbsd,!darwin,!windows
package backtrace
import (
"net"
"syscall"
"github.com/oneclickvirt/backtrace/model"
. "github.com/oneclickvirt/defaultset"
)
func (t *Tracer) listen(network string, laddr *net.IPAddr) (*net.IPConn, error) {
if model.EnableLoger {
InitLogger()
defer Logger.Sync()
}
conn, err := net.ListenIP(network, laddr)
if err != nil {
if model.EnableLoger {
Logger.Info(err.Error())
}
return nil, err
}
raw, err := conn.SyscallConn()
if err != nil {
if model.EnableLoger {
Logger.Info(err.Error())
}
conn.Close()
return nil, err
}
_ = raw.Control(func(fd uintptr) {
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_HDRINCL, 1)
})
if err != nil {
if model.EnableLoger {
Logger.Info(err.Error())
}
conn.Close()
return nil, err
}
return conn, nil
}

View File

@ -6,28 +6,22 @@ package backtrace
import ( import (
"net" "net"
"github.com/oneclickvirt/backtrace/model"
. "github.com/oneclickvirt/defaultset" . "github.com/oneclickvirt/defaultset"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
) )
func (t *Tracer) listen(network string, laddr *net.IPAddr) (*net.IPConn, error) { func (t *Tracer) listen(network string, laddr *net.IPAddr) (*net.IPConn, error) {
if model.EnableLoger { if EnableLoger {
InitLogger() InitLogger()
defer Logger.Sync() defer Logger.Sync()
}
conn, err := net.ListenIP(network, laddr) conn, err := net.ListenIP(network, laddr)
if err != nil { if err != nil {
if model.EnableLoger {
Logger.Info(err.Error()) Logger.Info(err.Error())
}
return nil, err return nil, err
} }
raw, err := conn.SyscallConn() raw, err := conn.SyscallConn()
if err != nil { if err != nil {
if model.EnableLoger {
Logger.Info(err.Error()) Logger.Info(err.Error())
}
conn.Close() conn.Close()
return nil, err return nil, err
} }
@ -35,11 +29,28 @@ func (t *Tracer) listen(network string, laddr *net.IPAddr) (*net.IPConn, error)
err = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IP, windows.IP_HDRINCL, 1) err = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IP, windows.IP_HDRINCL, 1)
}) })
if err != nil { if err != nil {
if model.EnableLoger {
Logger.Info(err.Error()) Logger.Info(err.Error())
}
conn.Close() conn.Close()
return nil, err return nil, err
} }
return conn, nil return conn, nil
} else {
conn, err := net.ListenIP(network, laddr)
if err != nil {
return nil, err
}
raw, err := conn.SyscallConn()
if err != nil {
conn.Close()
return nil, err
}
_ = raw.Control(func(fd uintptr) {
err = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IP, windows.IP_HDRINCL, 1)
})
if err != nil {
conn.Close()
return nil, err
}
return conn, nil
}
} }

View File

@ -1,27 +0,0 @@
2400:9380:9115
2400:9380:9116
2400:9380:9206
2400:9380:9262
2400:9380:a003
2400:9380:a00a
2400:9380:a00c
2400:9380:a01f
2400:9380:a022
2400:9380:a026
2400:9380:a028
2400:9380:a042
2400:9380:a110
2804:1e48:9003
2a04:f580:9001
2a04:f580:9090
2a04:f581:110a
2a04:f581:110b
2a04:f581:a123
2a04:f581:a125
2c0f:f7a8:1
2c0f:f7a8:2
2c0f:f7a8:24
2c0f:f7a8:29
2c0f:f7a8:37
2c0f:f7a8:47
2c0f:f7a8:9011

View File

@ -1,225 +0,0 @@
2400:9380:8001
2400:9380:8003
2400:9380:8021
2400:9380:8040
2400:9380:8140
2400:9380:8201
2400:9380:8301
240e
240e:1
240e:100
240e:101
240e:103
240e:104
240e:106
240e:108
240e:11:8001
240e:12
240e:144
240e:16:1001
240e:16:1008
240e:16:1009
240e:184
240e:1c7
240e:1c:112
240e:2
240e:218
240e:219
240e:21a
240e:21b
240e:224
240e:225
240e:226
240e:227
240e:24
240e:240
240e:241
240e:242
240e:243
240e:244
240e:245
240e:246
240e:247
240e:2c
240e:318
240e:319
240e:31a
240e:31b
240e:324
240e:325
240e:326
240e:327
240e:340
240e:341
240e:342
240e:343
240e:350:205
240e:41
240e:418
240e:419
240e:41a
240e:41b
240e:42
240e:424
240e:425
240e:426
240e:427
240e:42:4000
240e:43
240e:43:8000
240e:44
240e:440
240e:441
240e:442
240e:443
240e:444
240e:445
240e:446
240e:447
240e:45
240e:46
240e:46:5008
240e:47
240e:47:4
240e:48
240e:49
240e:4a
240e:4a:4100
240e:4b
240e:4b:2
240e:4c
240e:4c:4006
240e:4d
240e:4d:50ff
240e:4e
240e:4e:4000
240e:4f
240e:50
240e:51
240e:518
240e:519
240e:51a
240e:51b
240e:52
240e:524
240e:525
240e:526
240e:527
240e:52:4802
240e:53
240e:54
240e:540
240e:541
240e:542
240e:543
240e:544
240e:545
240e:546
240e:547
240e:55
240e:56
240e:56:4807
240e:57
240e:5a
240e:5a:6800
240e:5b
240e:5c
240e:5d
240e:5e
240e:5f
240e:618
240e:619
240e:61a
240e:61b
240e:61d:1401
240e:624
240e:625
240e:626
240e:627
240e:638
240e:64
240e:640
240e:641
240e:642
240e:643
240e:659:1160
240e:7
240e:718
240e:719
240e:71a
240e:71b
240e:724
240e:725
240e:726
240e:727
240e:740
240e:741
240e:742
240e:743
240e:767
240e:840
240e:841
240e:9
240e:918
240e:919
240e:91a
240e:91b
240e:924
240e:925
240e:926
240e:927
240e:940
240e:941
240e:942
240e:943
240e:944
240e:945
240e:946
240e:947
240e:980
240e:981
240e:982
240e:983
240e:a0c
240e:a0d
240e:a0e
240e:a0f
240e:a18
240e:a19
240e:a1a
240e:a1b
240e:a24
240e:a25
240e:a26
240e:a27
240e:a40
240e:a41
240e:a42
240e:a43
240e:a44
240e:a45
240e:a46
240e:a47
240e:a5:8000
240e:b
240e:bd:8000
240e:cd:8000
240e:d:100
2605:9d80:8001
2605:9d80:8011
2605:9d80:8021
2605:9d80:8031
2605:9d80:8041
2605:9d80:8081
2a04:f580:8010
2a04:f580:8011
2a04:f580:8090
2a04:f580:8210
2a04:f580:8211
2a04:f580:8290
2c0f:f7a8:8011
2c0f:f7a8:8050
2c0f:f7a8:805f
2c0f:f7a8:8150
2c0f:f7a8:815f
2c0f:f7a8:8211

View File

@ -1,161 +0,0 @@
2400:9380:9001
2400:9380:9002
2400:9380:9005
2400:9380:9009
2400:9380:900a
2400:9380:9020
2400:9380:9021
2400:9380:9040
2400:9380:9050
2400:9380:9051
2400:9380:9060
2400:9380:9071
2400:9380:9080
2400:9380:9081
2400:9380:90b0
2400:9380:90b1
2400:9380:90b2
2400:9380:90b3
2400:9380:90b4
2400:9380:90b5
2400:9380:90b6
2400:9380:90b7
2400:9380:9100
2400:9380:9101
2400:9380:9121
2400:9380:9201
2400:9380:9202
2400:9380:9220
2400:9380:9221
2400:9380:9240
2400:9380:9241
2400:9380:9250
2400:9380:9251
2400:9380:9260
2400:9380:9271
2400:9380:9272
2400:9380:9280
2400:9380:9281
2400:9380:9282
2400:9380:92b0
2400:9380:92b1
2400:9380:92b2
2400:9380:92b3
2400:9380:92b4
2400:9380:92b5
2400:9380:92b6
2400:9380:92b7
240e:182:5401
240e:182:5501
240e:409:9000
240e:409:9001
240e:410:ff00
240e:411:ff00
240e:414
240e:43d:fff1
240e:43d:fff3
240e:43d:fff4
240e:43d:fff7
240e:440:ac00
240e:440:ac01
240e:440:ac02
240e:441:ac00
240e:441:ac01
240e:441:ac02
240e:445:3f00
240e:446:3f00
240e:451:bfc0
240e:451:bfe0
240e:451:ffa0
240e:451:ffc0
240e:451:ffe0
240e:456:fe00
240e:457:fe00
240e:469:f400
240e:476:febf
240e:476:feff
240e:604:314
240e:604:319
240e:60e
240e:60e:8000
240e:60e:8001
240e:615
240e:61d
240e:62c
240e:638:f
240e:63c
240e:640:178
240e:640:179
240e:640:17a
240e:640:17b
240e:645
240e:648:1e
240e:648:c40f
240e:648:c80f
240e:648:cc0f
240e:648:d00f
240e:648:d40f
240e:648:d80f
240e:648:dc0f
240e:649:c00f
240e:649:c40f
240e:649:c80f
240e:649:cc0f
240e:649:d00f
240e:649:d40f
240e:649:d80f
240e:649:dc0f
240e:64e
240e:650
240e:659:f100
240e:669
240e:670
240e:688:ed00
240e:699
240e:699:7a00
240e:699:7a01
240e:713:f020
240e:713:f021
240e:733:4c0
240e:767:f000
240e:7af
240e:eb
240e:f6:8002
240e:f6:8003
2605:9d80:9003
2605:9d80:9013
2605:9d80:9023
2605:9d80:9033
2605:9d80:9042
2605:9d80:9071
2605:9d80:9092
2804:1e48
2804:1e48:9001
2804:1e48:9002
2a04:f580:9010
2a04:f580:9012
2a04:f580:9013
2a04:f580:9020
2a04:f580:9030
2a04:f580:9040
2a04:f580:9050
2a04:f580:9060
2a04:f580:9070
2a04:f580:9080
2a04:f580:9210
2a04:f580:9212
2a04:f580:9213
2a04:f580:9220
2a04:f580:9230
2a04:f580:9240
2a04:f580:9250
2a04:f580:9260
2a04:f580:9270
2a04:f580:9280
2a04:f580:9290
2c0f:f7a8:9010
2c0f:f7a8:9020
2c0f:f7a8:9041
2c0f:f7a8:9210
2c0f:f7a8:9211
2c0f:f7a8:9220

View File

@ -1,414 +0,0 @@
2402:f140:ff13
2402:f140:ff14
2404:6500:dcb3
2405:1480
2406:1e40
2406:cac0
2407:6c40
2407:6c40:1500
2408
2408:8000
2408:8000:10fe
2408:8000:10ff
2408:8000:2
2408:8000:3
2408:8000:5005
2408:8001
2408:802a
2408:802c
2408:803e
2408:8056
2408:80c2
2408:80c5
2408:80e2
2408:80e9
2408:80f5
2408:80f9
2408:815f
2408:8181
2408:8182
2408:8183
2408:81a2
2408:81a3
2408:8210
2408:8211
2408:8212
2408:8213
2408:8214
2408:8215
2408:821a
2408:821b
2408:8220
2408:8221
2408:8226
2408:822a
2408:822b
2408:822e
2408:822f
2408:8230
2408:8231
2408:8232
2408:8233
2408:8234
2408:8235
2408:8236
2408:8237
2408:8238
2408:8239
2408:823c
2408:823d
2408:8240
2408:8248
2408:8249
2408:824a
2408:824b
2408:824c
2408:824e
2408:824f
2408:8250
2408:8251
2408:8252
2408:8253
2408:8254
2408:825c
2408:825d
2408:825f
2408:8260
2408:8262
2408:8263
2408:8264
2408:8265
2408:8266
2408:826a
2408:826c
2408:826d
2408:826e
2408:826f
2408:8270
2408:8274
2408:8275
2408:8276
2408:8277
2408:8278
2408:8279
2408:827a
2408:8310
2408:8311
2408:8312
2408:8313
2408:832a
2408:832e
2408:832f
2408:8330
2408:8331
2408:8332
2408:8333
2408:8338
2408:8340
2408:8348
2408:8349
2408:834a
2408:834b
2408:834e
2408:834f
2408:8350
2408:8351
2408:8352
2408:8353
2408:8354
2408:8360
2408:8361
2408:8362
2408:8363
2408:8364
2408:8365
2408:836c
2408:836d
2408:836e
2408:836f
2408:8374
2408:8375
2408:8376
2408:8377
2408:8378
2408:8379
2408:837a
2408:8410
2408:8411
2408:8412
2408:8413
2408:8414
2408:8415
2408:8417
2408:8418
2408:841a
2408:841b
2408:841c
2408:841d
2408:841e
2408:8420
2408:8421
2408:8422
2408:8426
2408:8427
2408:842a
2408:842b
2408:842c
2408:842e
2408:8430
2408:8431
2408:8434
2408:8435
2408:8436
2408:8437
2408:8438
2408:8439
2408:843c
2408:843d
2408:843e
2408:843f
2408:8440
2408:8441
2408:8448
2408:844b
2408:844c
2408:844d
2408:844e
2408:844f
2408:8452
2408:8453
2408:8454
2408:8459
2408:845c
2408:845d
2408:8460
2408:8461
2408:8462
2408:8463
2408:8464
2408:8465
2408:8466
2408:8469
2408:846a
2408:846b
2408:846c
2408:846d
2408:846e
2408:846f
2408:8470
2408:8471
2408:8474
2408:8475
2408:8476
2408:8477
2408:8478
2408:8479
2408:847a
2408:84e3
2408:84e4
2408:84e5
2408:84e6
2408:84e7
2408:84e9
2408:84eb
2408:84ec
2408:84ed
2408:84ee
2408:84ef
2408:84f0
2408:84f1
2408:84f2
2408:84f4
2408:84f5
2408:84f6
2408:84f7
2408:84f8
2408:84f9
2408:84fa
2408:84fb
2408:84fc
2408:84fd
2408:84fe
2408:84ff
2408:856c
2408:856d
2408:8610
2408:8611
2408:8612
2408:8613
2408:8614
2408:8614:e20
2408:8615
2408:861a
2408:861b
2408:861c
2408:8620
2408:8621
2408:8624
2408:8625
2408:8626
2408:862a
2408:862b
2408:862d
2408:862e
2408:862f
2408:8630
2408:8631
2408:8632
2408:8633
2408:8634
2408:8635
2408:8636
2408:8637
2408:8638
2408:8639
2408:863c
2408:863d
2408:8640
2408:8642
2408:8648
2408:8649
2408:8649:5a00
2408:864c
2408:864e
2408:864f
2408:8650
2408:8651
2408:8652
2408:8653
2408:865c
2408:865d
2408:865f
2408:8660
2408:8662
2408:8663
2408:8664
2408:8665
2408:8666
2408:866a
2408:866b
2408:866c
2408:866d
2408:866e
2408:866f
2408:8670
2408:8674
2408:8675
2408:8676
2408:8677
2408:8678
2408:8679
2408:867a
2408:8710
2408:8711
2408:8712
2408:8713
2408:8719
2408:871a
2408:871b
2408:8720
2408:8721
2408:8722
2408:8723
2408:8726
2408:872b
2408:872f
2408:8730
2408:8731
2408:8732
2408:8733
2408:8734
2408:8735
2408:8736
2408:8738
2408:873c
2408:873d
2408:8740
2408:8742
2408:8748
2408:8749
2408:874a
2408:874b
2408:874c
2408:874d
2408:874e
2408:874f
2408:8752
2408:875c
2408:8760
2408:8762
2408:8763
2408:8764
2408:8765
2408:8766
2408:8768
2408:876a
2408:876c
2408:876d
2408:876e
2408:876f
2408:8770
2408:8772
2408:8773
2408:8774
2408:8776
2408:8777
2408:8778
2408:8779
2408:877a
2408:8812
2408:8813
2408:8814
2408:8815
2408:8818
2408:8819
2408:882c
2408:883a
2408:8862
2408:8863
2408:8864
2408:8865
2408:8866
2408:886e
2408:886f
2408:8872
2408:8878
2408:8879
2408:887e
2408:8912
2408:8913
2408:8914
2408:8915
2408:8916
2408:8917
2408:891c
2408:8920
2408:8924
2408:892c
2408:8936
2408:893a
2408:8940
2408:8948
2408:894c
2408:894e
2408:8956
2408:8957
2408:8962
2408:8963
2408:8964
2408:8965
2408:8966
2408:896c
2408:896e
2408:896f
2408:8972
2408:8978
2408:8979
2408:897a
2408:897b
2408:897e
2408:8a21
2408:8a23
2408:8a24
2408:8a26
2408:8a27

View File

@ -1,19 +0,0 @@
2001:43f8:1f0
2001:7f8:4
2001:7f8:43
2001:7fa:0:1
2400:8800:1f0e:5f
2400:8800:1f11:13
2401:cf80:620f:1
2402:4f00
2402:4f00:3000
2402:4f00:4000:4
2402:4f00:4003
2620:107:4008:1c2
2620:107:4008:1d9
2620:107:4008:1e8
2620:107:4008:bbef
2620:107:4008:bd27
2620:107:4008:d23c
2620:107:4008:d23d
2620:107:4008:d261

View File

@ -1,2 +0,0 @@
2402:4f00
2402:4f00:f000

File diff suppressed because it is too large Load Diff

View File

@ -1,57 +0,0 @@
2408:8120
2408:8120:1
2408:8120:2
2408:8256:226d
2408:8256:228b
2408:8411:c0c0
2408:8411:c0c1
2408:8411:c0c2
2408:8411:c0c3
2408:8411:c0c4
2408:8411:c0c5
2408:8417
2408:8418
2408:8435
2408:843e:e010
2408:843e:e011
2408:843e:e012
2408:843e:e080
2408:843e:e081
2408:843e:e082
2408:8441
2408:8444
2408:8474
2408:847a:ff20
2408:847a:ff21
2408:847a:ff22
2408:847a:ff40
2408:847a:ff41
2408:847a:ff42
2408:8610:3bff
2408:8614:1f0
2408:861c:1fff
2408:8625:10fb
2408:8626:f200
2408:862b
2408:862e:2ff
2408:8638:116
2408:8640
2408:8649:2a00
2408:8652:ff00
2408:8656:a52
2408:8660:100
2408:8660:ab00
2408:866c:ff00
2408:8678:1400
2408:8756:3efd
2408:8a00
2408:8a01
2408:8a02
2408:8a04
2408:8a06
2408:8a06:1
2408:8a06:100
2408:8a06:101
2408:8a26:ee10
2408:8a26:f020
2408:8a26:f950

View File

@ -3,19 +3,14 @@ package backtrace
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"net"
"sort"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/oneclickvirt/backtrace/model"
. "github.com/oneclickvirt/defaultset"
"golang.org/x/net/icmp" "golang.org/x/net/icmp"
"golang.org/x/net/ipv4" "golang.org/x/net/ipv4"
"golang.org/x/net/ipv6" "golang.org/x/net/ipv6"
"net"
"sort"
"sync"
"sync/atomic"
"time"
) )
// DefaultConfig is the default configuration for Tracer. // DefaultConfig is the default configuration for Tracer.
@ -24,7 +19,7 @@ var DefaultConfig = Config{
Timeout: 500 * time.Millisecond, Timeout: 500 * time.Millisecond,
MaxHops: 15, MaxHops: 15,
Count: 1, Count: 1,
Networks: []string{"ip4:icmp", "ip4:ip", "ip6:ipv6-icmp", "ip6:ip"}, Networks: []string{"ip4:icmp", "ip4:ip"},
} }
// DefaultTracer is a tracer with DefaultConfig. // DefaultTracer is a tracer with DefaultConfig.
@ -48,8 +43,7 @@ type Tracer struct {
Config Config
once sync.Once once sync.Once
conn *net.IPConn // Ipv4连接 conn *net.IPConn
ipv6conn *ipv6.PacketConn // IPv6连接
err error err error
mu sync.RWMutex mu sync.RWMutex
@ -119,36 +113,13 @@ func (t *Tracer) NewSession(ip net.IP) (*Session, error) {
} }
func (t *Tracer) init() { func (t *Tracer) init() {
// 初始化IPv4连接
for _, network := range t.Networks { for _, network := range t.Networks {
if strings.HasPrefix(network, "ip4") {
t.conn, t.err = t.listen(network, t.Addr) t.conn, t.err = t.listen(network, t.Addr)
if t.err == nil { if t.err != nil {
go t.serve(t.conn)
break
}
}
}
// 初始化IPv6连接
for _, network := range t.Networks {
if strings.HasPrefix(network, "ip6") {
conn, err := net.ListenIP(network, t.Addr)
if err == nil {
t.ipv6conn = ipv6.NewPacketConn(conn)
err = t.ipv6conn.SetControlMessage(ipv6.FlagHopLimit|ipv6.FlagSrc|ipv6.FlagDst|ipv6.FlagInterface, true)
if err != nil {
if model.EnableLoger {
InitLogger()
defer Logger.Sync()
Logger.Info("设置IPv6控制消息失败: " + err.Error())
}
t.ipv6conn.Close()
continue continue
} }
go t.serveIPv6(t.ipv6conn) go t.serve(t.conn)
break return
}
}
} }
} }
@ -160,10 +131,8 @@ func (t *Tracer) Close() {
if t.conn != nil { if t.conn != nil {
t.conn.Close() t.conn.Close()
} }
if t.ipv6conn != nil {
t.ipv6conn.Close()
}
} }
func (t *Tracer) serve(conn *net.IPConn) error { func (t *Tracer) serve(conn *net.IPConn) error {
defer conn.Close() defer conn.Close()
buf := make([]byte, 1500) buf := make([]byte, 1500)
@ -181,60 +150,17 @@ func (t *Tracer) serve(conn *net.IPConn) error {
func (t *Tracer) serveData(from net.IP, b []byte) error { func (t *Tracer) serveData(from net.IP, b []byte) error {
if from.To4() == nil { if from.To4() == nil {
// IPv6处理 // TODO: implement ProtocolIPv6ICMP
msg, err := icmp.ParseMessage(ProtocolIPv6ICMP, b) return errUnsupportedProtocol
if err != nil {
if model.EnableLoger {
Logger.Warn("解析IPv6 ICMP消息失败: " + err.Error())
} }
return err now := time.Now()
}
// 记录所有收到的消息类型,帮助调试
if model.EnableLoger {
Logger.Info(fmt.Sprintf("收到IPv6 ICMP消息: 类型=%v, 代码=%v", msg.Type, msg.Code))
}
// 处理不同类型的ICMP消息
switch msg.Type {
case ipv6.ICMPTypeEchoReply:
if echo, ok := msg.Body.(*icmp.Echo); ok {
if model.EnableLoger {
Logger.Info(fmt.Sprintf("处理IPv6回显应答: ID=%d, Seq=%d", echo.ID, echo.Seq))
}
return t.serveReply(from, &packet{from, uint16(echo.ID), 1, time.Now()})
}
case ipv6.ICMPTypeTimeExceeded:
b = getReplyData(msg)
if len(b) < ipv6.HeaderLen {
if model.EnableLoger {
Logger.Warn("IPv6时间超过消息太短")
}
return errMessageTooShort
}
// 解析原始IPv6包头
if b[0]>>4 == ipv6.Version {
ip, err := ipv6.ParseHeader(b)
if err != nil {
if model.EnableLoger {
Logger.Warn("解析IPv6头部失败: " + err.Error())
}
return err
}
if model.EnableLoger {
Logger.Info(fmt.Sprintf("处理IPv6时间超过: 目标=%v, FlowLabel=%d, HopLimit=%d",
ip.Dst, ip.FlowLabel, ip.HopLimit))
}
return t.serveReply(ip.Dst, &packet{from, uint16(ip.FlowLabel), ip.HopLimit, time.Now()})
}
}
} else {
// 原有的IPv4处理逻辑
msg, err := icmp.ParseMessage(ProtocolICMP, b) msg, err := icmp.ParseMessage(ProtocolICMP, b)
if err != nil { if err != nil {
return err return err
} }
if msg.Type == ipv4.ICMPTypeEchoReply { if msg.Type == ipv4.ICMPTypeEchoReply {
echo := msg.Body.(*icmp.Echo) echo := msg.Body.(*icmp.Echo)
return t.serveReply(from, &packet{from, uint16(echo.ID), 1, time.Now()}) return t.serveReply(from, &packet{from, uint16(echo.ID), 1, now})
} }
b = getReplyData(msg) b = getReplyData(msg)
if len(b) < ipv4.HeaderLen { if len(b) < ipv4.HeaderLen {
@ -246,46 +172,27 @@ func (t *Tracer) serveData(from net.IP, b []byte) error {
if err != nil { if err != nil {
return err return err
} }
return t.serveReply(ip.Dst, &packet{from, uint16(ip.ID), ip.TTL, time.Now()}) return t.serveReply(ip.Dst, &packet{from, uint16(ip.ID), ip.TTL, now})
case ipv6.Version:
ip, err := ipv6.ParseHeader(b)
if err != nil {
return err
}
return t.serveReply(ip.Dst, &packet{from, uint16(ip.FlowLabel), ip.HopLimit, now})
default: default:
return errUnsupportedProtocol return errUnsupportedProtocol
} }
}
return nil
} }
func (t *Tracer) sendRequest(dst net.IP, ttl int) (*packet, error) { func (t *Tracer) sendRequest(dst net.IP, ttl int) (*packet, error) {
id := uint16(atomic.AddUint32(&t.seq, 1)) id := uint16(atomic.AddUint32(&t.seq, 1))
var b []byte b := newPacket(id, dst, ttl)
req := &packet{dst, id, ttl, time.Now()} req := &packet{dst, id, ttl, time.Now()}
if dst.To4() == nil {
// IPv6
b := newPacketV6(id, dst, ttl)
if t.ipv6conn != nil {
cm := &ipv6.ControlMessage{
HopLimit: ttl,
}
_, err := t.ipv6conn.WriteTo(b, cm, &net.IPAddr{IP: dst})
if err != nil {
if model.EnableLoger {
InitLogger()
defer Logger.Sync()
Logger.Info("发送IPv6请求失败: " + err.Error())
}
return nil, err
}
return req, nil
}
return nil, errors.New("IPv6连接不可用")
} else {
// IPv4
b = newPacketV4(id, dst, ttl)
_, err := t.conn.WriteToIP(b, &net.IPAddr{IP: dst}) _, err := t.conn.WriteToIP(b, &net.IPAddr{IP: dst})
if err != nil { if err != nil {
return nil, err return nil, err
} }
return req, nil return req, nil
}
} }
func (t *Tracer) addSession(s *Session) { func (t *Tracer) addSession(s *Session) {
@ -310,30 +217,10 @@ func (t *Tracer) removeSession(s *Session) {
} }
func (t *Tracer) serveReply(dst net.IP, res *packet) error { func (t *Tracer) serveReply(dst net.IP, res *packet) error {
if model.EnableLoger {
Logger.Info(fmt.Sprintf("处理回复: 目标=%v, 来源=%v, ID=%d, TTL=%d",
dst, res.IP, res.ID, res.TTL))
}
// 确保使用正确的IP格式进行查找
shortDst := shortIP(dst)
t.mu.RLock() t.mu.RLock()
defer t.mu.RUnlock() defer t.mu.RUnlock()
// // 调试输出会话信息 a := t.sess[string(shortIP(dst))]
// if model.EnableLoger && len(t.sess) > 0 {
// for ip, sessions := range t.sess {
// Logger.Info(fmt.Sprintf("会话信息: IP=%v, 会话数=%d",
// net.IP([]byte(ip)), len(sessions)))
// }
// }
// 查找对应的会话
a := t.sess[string(shortDst)]
if len(a) == 0 && model.EnableLoger {
Logger.Warn(fmt.Sprintf("找不到目标IP=%v的会话", dst))
}
for _, s := range a { for _, s := range a {
// if model.EnableLoger {
// Logger.Info(fmt.Sprintf("处理会话响应: 会话目标=%v", s.ip))
// }
s.handle(res) s.handle(res)
} }
return nil return nil
@ -397,32 +284,12 @@ func (s *Session) handle(res *packet) {
now := res.Time now := res.Time
n := 0 n := 0
var req *packet var req *packet
if model.EnableLoger {
Logger.Info(fmt.Sprintf("处理会话响应: 会话目标=%v, 响应源=%v, ID=%d, TTL=%d",
s.ip, res.IP, res.ID, res.TTL))
}
s.mu.Lock() s.mu.Lock()
// // 打印出所有待处理的探测包
// if model.EnableLoger && len(s.probes) > 0 {
// Logger.Info(fmt.Sprintf("当前会话有 %d 个待处理的探测包", len(s.probes)))
// for i, probe := range s.probes {
// Logger.Info(fmt.Sprintf("探测包 #%d: ID=%d, TTL=%d, 时间=%v",
// i, probe.ID, probe.TTL, probe.Time))
// }
// }
// 查找匹配的请求包
for _, r := range s.probes { for _, r := range s.probes {
if now.Sub(r.Time) > s.t.Timeout { if now.Sub(r.Time) > s.t.Timeout {
// if model.EnableLoger {
// Logger.Info(fmt.Sprintf("探测包超时: ID=%d, TTL=%d", r.ID, r.TTL))
// }
continue continue
} }
// 对于IPv6 松散匹配 if r.ID == res.ID {
if r.ID == res.ID || res.IP.To4() == nil {
// if model.EnableLoger {
// Logger.Info(fmt.Sprintf("找到匹配的探测包: ID=%d, TTL=%d", r.ID, r.TTL))
// }
req = r req = r
continue continue
} }
@ -432,19 +299,12 @@ func (s *Session) handle(res *packet) {
s.probes = s.probes[:n] s.probes = s.probes[:n]
s.mu.Unlock() s.mu.Unlock()
if req == nil { if req == nil {
// if model.EnableLoger {
// Logger.Warn(fmt.Sprintf("未找到匹配的探测包: 响应ID=%d", res.ID))
// }
return return
} }
hops := req.TTL - res.TTL + 1 hops := req.TTL - res.TTL + 1
if hops < 1 { if hops < 1 {
hops = 1 hops = 1
} }
if model.EnableLoger {
Logger.Info(fmt.Sprintf("创建响应: IP=%v, RTT=%v, Hops=%d",
res.IP, res.Time.Sub(req.Time), hops))
}
select { select {
case s.ch <- &Reply{ case s.ch <- &Reply{
IP: res.IP, IP: res.IP,
@ -452,9 +312,6 @@ func (s *Session) handle(res *packet) {
Hops: hops, Hops: hops,
}: }:
default: default:
if model.EnableLoger {
Logger.Warn("发送响应到通道失败,通道已满")
}
} }
} }
@ -495,6 +352,33 @@ var (
errNoReplyData = errors.New("no reply data") errNoReplyData = errors.New("no reply data")
) )
func newPacket(id uint16, dst net.IP, ttl int) []byte {
// TODO: reuse buffers...
msg := icmp.Message{
Type: ipv4.ICMPTypeEcho,
Body: &icmp.Echo{
ID: int(id),
Seq: int(id),
},
}
p, _ := msg.Marshal(nil)
ip := &ipv4.Header{
Version: ipv4.Version,
Len: ipv4.HeaderLen,
TotalLen: ipv4.HeaderLen + len(p),
TOS: 16,
ID: int(id),
Dst: dst,
Protocol: ProtocolICMP,
TTL: ttl,
}
buf, err := ip.Marshal()
if err != nil {
return nil
}
return append(buf, p...)
}
// IANA Assigned Internet Protocol Numbers // IANA Assigned Internet Protocol Numbers
const ( const (
ProtocolICMP = 1 ProtocolICMP = 1

View File

@ -1,206 +0,0 @@
package backtrace
import (
"fmt"
"net"
"strings"
"sync"
"github.com/oneclickvirt/backtrace/model"
. "github.com/oneclickvirt/defaultset"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
)
func newPacketV4(id uint16, dst net.IP, ttl int) []byte {
// TODO: reuse buffers...
msg := icmp.Message{
Type: ipv4.ICMPTypeEcho,
Body: &icmp.Echo{
ID: int(id),
Seq: int(id),
},
}
p, _ := msg.Marshal(nil)
ip := &ipv4.Header{
Version: ipv4.Version,
Len: ipv4.HeaderLen,
TotalLen: ipv4.HeaderLen + len(p),
TOS: 16,
ID: int(id),
Dst: dst,
Protocol: ProtocolICMP,
TTL: ttl,
}
buf, err := ip.Marshal()
if err != nil {
return nil
}
return append(buf, p...)
}
// extractIpv4ASNsFromHops 从跃点中提取ASN列表
func extractIpv4ASNsFromHops(hops []*Hop, enableLogger bool) []string {
var asns []string
for _, h := range hops {
for _, n := range h.Nodes {
asn := ipv4Asn(n.IP.String())
if asn != "" {
asns = append(asns, asn)
if enableLogger {
Logger.Info(fmt.Sprintf("IP %s 对应的ASN: %s", n.IP.String(), asn))
}
}
}
}
return asns
}
// trace IPv4追踪函数
func trace(ch chan Result, i int) {
if model.EnableLoger {
InitLogger()
defer Logger.Sync()
Logger.Info(fmt.Sprintf("开始追踪 %s (%s)", model.Ipv4Names[i], model.Ipv4s[i]))
}
var allHops [][]*Hop
var successfulTraces int
var mu sync.Mutex
var wg sync.WaitGroup
// 并发执行3次trace
for attempt := 1; attempt <= 3; attempt++ {
wg.Add(1)
go func(attemptNum int) {
defer wg.Done()
if model.EnableLoger {
Logger.Info(fmt.Sprintf("第%d次尝试追踪 %s (%s)", attemptNum, model.Ipv4Names[i], model.Ipv4s[i]))
}
// 先尝试原始IP地址
hops, err := Trace(net.ParseIP(model.Ipv4s[i]))
if err != nil {
if model.EnableLoger {
Logger.Warn(fmt.Sprintf("第%d次追踪 %s (%s) 失败: %v", attemptNum, model.Ipv4Names[i], model.Ipv4s[i], err))
}
// 如果原始IP失败尝试备选IP
if tryAltIPs := tryAlternativeIPs(model.Ipv4Names[i], "v4"); len(tryAltIPs) > 0 {
for _, altIP := range tryAltIPs {
if model.EnableLoger {
Logger.Info(fmt.Sprintf("第%d次尝试备选IP %s 追踪 %s", attemptNum, altIP, model.Ipv4Names[i]))
}
hops, err = Trace(net.ParseIP(altIP))
if err == nil && len(hops) > 0 {
break // 成功找到可用IP
}
}
}
}
if err == nil && len(hops) > 0 {
mu.Lock()
allHops = append(allHops, hops)
successfulTraces++
mu.Unlock()
if model.EnableLoger {
Logger.Info(fmt.Sprintf("第%d次追踪 %s (%s) 成功,获得%d个hop", attemptNum, model.Ipv4Names[i], model.Ipv4s[i], len(hops)))
}
}
}(attempt)
}
// 等待所有goroutine完成
wg.Wait()
// 如果3次都失败
if successfulTraces == 0 {
s := fmt.Sprintf("%v %-15s %v", model.Ipv4Names[i], model.Ipv4s[i], Red("检测不到回程路由节点的IP地址"))
if model.EnableLoger {
Logger.Error(fmt.Sprintf("%s (%s) 3次尝试都失败检测不到回程路由节点的IP地址", model.Ipv4Names[i], model.Ipv4s[i]))
}
ch <- Result{i, s}
return
}
// 合并hops结果
mergedHops := mergeHops(allHops)
if model.EnableLoger {
Logger.Info(fmt.Sprintf("%s (%s) 完成%d次成功追踪合并后获得%d个hop", model.Ipv4Names[i], model.Ipv4s[i], successfulTraces, len(mergedHops)))
}
// 从合并后的hops提取ASN
asns := extractIpv4ASNsFromHops(mergedHops, model.EnableLoger)
// 处理不同线路
if len(asns) > 0 {
var tempText string
asns = removeDuplicates(asns)
tempText += fmt.Sprintf("%v ", model.Ipv4Names[i])
hasAS4134 := false
hasAS4809 := false
for _, asn := range asns {
if asn == "AS4134" {
hasAS4134 = true
}
if asn == "AS4809" {
hasAS4809 = true
}
}
// 判断是否包含 AS4134 和 AS4809
if hasAS4134 && hasAS4809 {
// 同时包含 AS4134 和 AS4809 属于 CN2GT
asns = append([]string{"AS4809b"}, asns...)
if model.EnableLoger {
Logger.Info(fmt.Sprintf("%s (%s) 线路识别为: CN2GT", model.Ipv4Names[i], model.Ipv4s[i]))
}
} else if hasAS4809 {
// 仅包含 AS4809 属于 CN2GIA
asns = append([]string{"AS4809a"}, asns...)
if model.EnableLoger {
Logger.Info(fmt.Sprintf("%s (%s) 线路识别为: CN2GIA", model.Ipv4Names[i], model.Ipv4s[i]))
}
}
tempText += fmt.Sprintf("%-24s ", model.Ipv4s[i])
for _, asn := range asns {
asnDescription := model.M[asn]
switch asn {
case "":
continue
case "AS4809": // 被 AS4809a 和 AS4809b 替代了
continue
case "AS9929":
if !strings.Contains(tempText, asnDescription) {
tempText += DarkGreen(asnDescription) + " "
}
case "AS4809a":
if !strings.Contains(tempText, asnDescription) {
tempText += DarkGreen(asnDescription) + " "
}
case "AS23764":
if !strings.Contains(tempText, asnDescription) {
tempText += DarkGreen(asnDescription) + " "
}
case "AS4809b":
if !strings.Contains(tempText, asnDescription) {
tempText += Green(asnDescription) + " "
}
case "AS58807":
if !strings.Contains(tempText, asnDescription) {
tempText += Green(asnDescription) + " "
}
default:
if !strings.Contains(tempText, asnDescription) {
tempText += White(asnDescription) + " "
}
}
}
if tempText == (fmt.Sprintf("%v ", model.Ipv4Names[i]) + fmt.Sprintf("%-15s ", model.Ipv4s[i])) {
tempText += fmt.Sprintf("%v", Red("检测不到已知线路的ASN"))
if model.EnableLoger {
Logger.Warn(fmt.Sprintf("%s (%s) 检测不到已知线路的ASN", model.Ipv4Names[i], model.Ipv4s[i]))
}
}
if model.EnableLoger {
Logger.Info(fmt.Sprintf("%s (%s) 追踪完成,最终结果: %s", model.Ipv4Names[i], model.Ipv4s[i], tempText))
}
ch <- Result{i, tempText}
} else {
s := fmt.Sprintf("%v %-15s %v", model.Ipv4Names[i], model.Ipv4s[i], Red("检测不到回程路由节点的IPV4地址"))
if model.EnableLoger {
Logger.Warn(fmt.Sprintf("%s (%s) 检测不到回程路由节点的IPV4地址", model.Ipv4Names[i], model.Ipv4s[i]))
}
ch <- Result{i, s}
}
}

View File

@ -1,221 +0,0 @@
package backtrace
import (
"fmt"
"net"
"strings"
"sync"
"github.com/oneclickvirt/backtrace/model"
. "github.com/oneclickvirt/defaultset"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv6"
)
func newPacketV6(id uint16, _ net.IP, _ int) []byte {
// 使用ipv6包的Echo请求
msg := icmp.Message{
Type: ipv6.ICMPTypeEchoRequest,
Code: 0,
Body: &icmp.Echo{
ID: int(id),
Seq: int(id),
Data: []byte("HELLO-R-U-THERE"),
},
}
// 序列化ICMP消息
icmpBytes, _ := msg.Marshal(nil)
return icmpBytes
}
func (t *Tracer) serveIPv6(conn *ipv6.PacketConn) error {
if model.EnableLoger {
InitLogger()
defer Logger.Sync()
}
defer conn.Close()
buf := make([]byte, 1500)
for {
n, cm, src, err := conn.ReadFrom(buf)
if err != nil {
if model.EnableLoger {
Logger.Error("读取IPv6响应失败: " + err.Error())
}
return err
}
if model.EnableLoger {
Logger.Info(fmt.Sprintf("收到IPv6响应: 来源=%v, 跳数=%d", src, cm.HopLimit))
}
fromIP := src.(*net.IPAddr).IP
err = t.serveData(fromIP, buf[:n])
if err != nil && model.EnableLoger {
Logger.Warn("处理IPv6数据失败: " + err.Error())
}
}
}
// extractIpv6ASNsFromHops 从跃点中提取ASN列表
func extractIpv6ASNsFromHops(hops []*Hop, enableLogger bool) []string {
var asns []string
for _, h := range hops {
for _, n := range h.Nodes {
asn := ipv6Asn(n.IP.String())
if asn != "" {
asns = append(asns, asn)
if enableLogger {
Logger.Info(fmt.Sprintf("IP %s 对应的ASN: %s", n.IP.String(), asn))
}
}
}
}
return asns
}
// traceIPv6 IPv6追踪函数
func traceIPv6(ch chan Result, i int, offset int) {
if model.EnableLoger {
InitLogger()
defer Logger.Sync()
Logger.Info(fmt.Sprintf("开始追踪 %s (%s)", model.Ipv6Names[i], model.Ipv6s[i]))
}
var allHops [][]*Hop
var successfulTraces int
var mu sync.Mutex
var wg sync.WaitGroup
// 并发执行3次trace
for attempt := 1; attempt <= 3; attempt++ {
wg.Add(1)
go func(attemptNum int) {
defer wg.Done()
if model.EnableLoger {
Logger.Info(fmt.Sprintf("第%d次尝试追踪 %s (%s)", attemptNum, model.Ipv6Names[i], model.Ipv6s[i]))
}
// 先尝试原始IP地址
hops, err := Trace(net.ParseIP(model.Ipv6s[i]))
if err != nil {
if model.EnableLoger {
Logger.Warn(fmt.Sprintf("第%d次追踪 %s (%s) 失败: %v", attemptNum, model.Ipv6Names[i], model.Ipv6s[i], err))
}
// 如果原始IP失败尝试备选IP
if tryAltIPs := tryAlternativeIPs(model.Ipv6Names[i], "v6"); len(tryAltIPs) > 0 {
for _, altIP := range tryAltIPs {
if model.EnableLoger {
Logger.Info(fmt.Sprintf("第%d次尝试备选IP %s 追踪 %s", attemptNum, altIP, model.Ipv6Names[i]))
}
hops, err = Trace(net.ParseIP(altIP))
if err == nil && len(hops) > 0 {
break // 成功找到可用IP
}
}
}
}
if err == nil && len(hops) > 0 {
mu.Lock()
allHops = append(allHops, hops)
successfulTraces++
mu.Unlock()
if model.EnableLoger {
Logger.Info(fmt.Sprintf("第%d次追踪 %s (%s) 成功,获得%d个hop", attemptNum, model.Ipv6Names[i], model.Ipv6s[i], len(hops)))
}
}
}(attempt)
}
// 等待所有goroutine完成
wg.Wait()
// 如果3次都失败
if successfulTraces == 0 {
s := fmt.Sprintf("%v %-24s %v", model.Ipv6Names[i], model.Ipv6s[i], Red("检测不到回程路由节点的IP地址"))
if model.EnableLoger {
Logger.Warn(fmt.Sprintf("%s (%s) 3次尝试都失败检测不到回程路由节点的IP地址", model.Ipv6Names[i], model.Ipv6s[i]))
}
ch <- Result{i + offset, s}
return
}
// 合并hops结果
mergedHops := mergeHops(allHops)
if model.EnableLoger {
Logger.Info(fmt.Sprintf("%s (%s) 完成%d次成功追踪合并后获得%d个hop", model.Ipv6Names[i], model.Ipv6s[i], successfulTraces, len(mergedHops)))
}
// 从合并后的hops提取ASN
asns := extractIpv6ASNsFromHops(mergedHops, model.EnableLoger)
// 处理不同线路
if len(asns) > 0 {
var tempText string
asns = removeDuplicates(asns)
tempText += fmt.Sprintf("%v ", model.Ipv6Names[i])
hasAS4134 := false
hasAS4809 := false
for _, asn := range asns {
if asn == "AS4134" {
hasAS4134 = true
}
if asn == "AS4809" {
hasAS4809 = true
}
}
// 判断是否包含 AS4134 和 AS4809
if hasAS4134 && hasAS4809 {
// 同时包含 AS4134 和 AS4809 属于 CN2GT
asns = append([]string{"AS4809b"}, asns...)
if model.EnableLoger {
Logger.Info(fmt.Sprintf("%s (%s) 线路识别为: CN2GT", model.Ipv6Names[i], model.Ipv6s[i]))
}
} else if hasAS4809 {
// 仅包含 AS4809 属于 CN2GIA
asns = append([]string{"AS4809a"}, asns...)
if model.EnableLoger {
Logger.Info(fmt.Sprintf("%s (%s) 线路识别为: CN2GIA", model.Ipv6Names[i], model.Ipv6s[i]))
}
}
tempText += fmt.Sprintf("%-24s ", model.Ipv6s[i])
for _, asn := range asns {
asnDescription := model.M[asn]
switch asn {
case "":
continue
case "AS4809": // 被 AS4809a 和 AS4809b 替代了
continue
case "AS9929":
if !strings.Contains(tempText, asnDescription) {
tempText += DarkGreen(asnDescription) + " "
}
case "AS4809a":
if !strings.Contains(tempText, asnDescription) {
tempText += DarkGreen(asnDescription) + " "
}
case "AS23764":
if !strings.Contains(tempText, asnDescription) {
tempText += DarkGreen(asnDescription) + " "
}
case "AS4809b":
if !strings.Contains(tempText, asnDescription) {
tempText += Green(asnDescription) + " "
}
case "AS58807":
if !strings.Contains(tempText, asnDescription) {
tempText += Green(asnDescription) + " "
}
default:
if !strings.Contains(tempText, asnDescription) {
tempText += White(asnDescription) + " "
}
}
}
if tempText == (fmt.Sprintf("%v ", model.Ipv6Names[i]) + fmt.Sprintf("%-40s ", model.Ipv6s[i])) {
tempText += fmt.Sprintf("%v", Red("检测不到已知线路的ASN"))
if model.EnableLoger {
Logger.Warn(fmt.Sprintf("%s (%s) 检测不到已知线路的ASN", model.Ipv6Names[i], model.Ipv6s[i]))
}
}
if model.EnableLoger {
Logger.Info(fmt.Sprintf("%s (%s) 追踪完成,最终结果: %s", model.Ipv6Names[i], model.Ipv6s[i], tempText))
}
ch <- Result{i + offset, tempText}
} else {
s := fmt.Sprintf("%v %-24s %v", model.Ipv6Names[i], model.Ipv6s[i], Red("检测不到回程路由节点的IPV6地址"))
if model.EnableLoger {
Logger.Warn(fmt.Sprintf("%s (%s) 检测不到回程路由节点的IPV6地址", model.Ipv6Names[i], model.Ipv6s[i]))
}
ch <- Result{i + offset, s}
}
}

View File

@ -1,275 +0,0 @@
package backtrace
import (
"encoding/json"
"fmt"
"io"
"strings"
"time"
"github.com/imroc/req/v3"
"github.com/oneclickvirt/backtrace/model"
. "github.com/oneclickvirt/defaultset"
)
type Result struct {
i int
s string
}
// removeDuplicates 切片去重
func removeDuplicates(elements []string) []string {
if elements == nil {
return nil
}
seen := make(map[string]struct{})
var result []string
for _, v := range elements {
if _, ok := seen[v]; !ok {
seen[v] = struct{}{}
result = append(result, v)
}
}
return result
}
// checkCdn 检查CDN可用性参考shell脚本的测试逻辑
func checkCdn(testUrl string) string {
client := req.C()
client.SetTimeout(6 * time.Second)
if model.EnableLoger {
InitLogger()
defer Logger.Sync()
}
for _, cdnUrl := range model.CdnList {
url := cdnUrl + testUrl
if model.EnableLoger {
Logger.Info(fmt.Sprintf("Testing CDN: %s", url))
}
resp, err := client.R().Get(url)
if err == nil {
b, err := io.ReadAll(resp.Body)
resp.Body.Close()
if err == nil && strings.Contains(string(b), "success") {
if model.EnableLoger {
Logger.Info(fmt.Sprintf("CDN available: %s", cdnUrl))
}
return cdnUrl
}
}
if model.EnableLoger {
Logger.Info(fmt.Sprintf("CDN test failed: %s, error: %v", cdnUrl, err))
}
time.Sleep(500 * time.Millisecond)
}
if model.EnableLoger {
Logger.Info("No CDN available, using direct connection")
}
return ""
}
// getData 获取目标地址的文本内容
func getData(endpoint string) string {
client := req.C()
client.SetTimeout(6 * time.Second)
client.R().
SetRetryCount(2).
SetRetryBackoffInterval(1*time.Second, 5*time.Second).
SetRetryFixedInterval(2 * time.Second)
if model.EnableLoger {
InitLogger()
defer Logger.Sync()
}
// 先测试CDN可用性
testUrl := "https://raw.githubusercontent.com/spiritLHLS/ecs/main/back/test"
cdnUrl := checkCdn(testUrl)
// 如果有可用的CDN使用CDN获取数据
if cdnUrl != "" {
url := cdnUrl + endpoint
if model.EnableLoger {
Logger.Info(fmt.Sprintf("Using CDN: %s", url))
}
resp, err := client.R().Get(url)
if err == nil {
defer resp.Body.Close()
b, err := io.ReadAll(resp.Body)
if err == nil && !strings.Contains(string(b), "error") {
if model.EnableLoger {
Logger.Info(fmt.Sprintf("Received data length: %d", len(b)))
}
return string(b)
}
}
if model.EnableLoger {
Logger.Info(fmt.Sprintf("CDN request failed: %v", err))
}
}
// CDN不可用尝试直连
if model.EnableLoger {
Logger.Info(fmt.Sprintf("Trying direct connection: %s", endpoint))
}
resp, err := client.R().Get(endpoint)
if err == nil {
defer resp.Body.Close()
b, err := io.ReadAll(resp.Body)
if err == nil {
if model.EnableLoger {
Logger.Info(fmt.Sprintf("Received data length: %d", len(b)))
}
return string(b)
}
}
if model.EnableLoger {
Logger.Info(fmt.Sprintf("Direct connection failed: %v", err))
}
return ""
}
// parseIcmpTargets 解析ICMP目标数据
func parseIcmpTargets(jsonData string) []model.IcmpTarget {
var targets []model.IcmpTarget
err := json.Unmarshal([]byte(jsonData), &targets)
if err != nil {
if model.EnableLoger {
Logger.Info(fmt.Sprintf("解析ICMP目标失败: %s", err.Error()))
}
return nil
}
return targets
}
// tryAlternativeIPs 从IcmpTargets获取备选IP地址
func tryAlternativeIPs(targetName string, ipVersion string) []string {
if model.ParsedIcmpTargets == nil || (model.ParsedIcmpTargets != nil && len(model.ParsedIcmpTargets) == 0) {
return nil
}
if model.EnableLoger {
Logger.Info(fmt.Sprintf("使用备选地址: %s %s", targetName, ipVersion))
}
// 从目标名称中提取省份和ISP信息
var targetProvince, targetISP string
if strings.Contains(targetName, "北京") {
targetProvince = "北京"
} else if strings.Contains(targetName, "上海") {
targetProvince = "上海"
} else if strings.Contains(targetName, "广州") {
targetProvince = "广东"
} else if strings.Contains(targetName, "成都") {
targetProvince = "四川"
}
if strings.Contains(targetName, "电信") {
targetISP = "电信"
} else if strings.Contains(targetName, "联通") {
targetISP = "联通"
} else if strings.Contains(targetName, "移动") {
targetISP = "移动"
}
// 如果没有提取到信息,返回空
if targetProvince == "" || targetISP == "" {
return nil
}
// 查找匹配条件的目标
var result []string
for _, target := range model.ParsedIcmpTargets {
// 检查省份是否匹配(可能带有"省"字或不带)
provinceMatch := (target.Province == targetProvince) || (target.Province == targetProvince+"省")
// 检查ISP和IP版本是否匹配
if provinceMatch && target.ISP == targetISP && target.IPVersion == ipVersion {
// 解析IP列表
if target.IPs != "" {
ips := strings.Split(target.IPs, ",")
// 最多返回3个IP地址
count := 0
for _, ip := range ips {
if ip != "" {
result = append(result, strings.TrimSpace(ip))
count++
if count >= 3 {
break
}
}
}
if len(result) > 0 {
return result
}
}
}
}
return nil
}
// mergeHops 合并多个hops结果
func mergeHops(allHops [][]*Hop) []*Hop {
if len(allHops) == 0 {
return nil
}
if len(allHops) == 1 {
return allHops[0]
}
// 找到最大长度
maxLen := 0
for _, hops := range allHops {
if len(hops) > maxLen {
maxLen = len(hops)
}
}
var mergedHops []*Hop
// 逐位置合并
for pos := 0; pos < maxLen; pos++ {
var availableHops []*Hop
// 收集当前位置所有可用的hop
for _, hops := range allHops {
if pos < len(hops) {
availableHops = append(availableHops, hops[pos])
}
}
if len(availableHops) == 0 {
continue
}
// 如果只有一个可用hop直接使用
if len(availableHops) == 1 {
mergedHops = append(mergedHops, availableHops[0])
continue
}
// 统计相同的hop通过比较第一个node的IP
hopCount := make(map[string][]*Hop)
for _, hop := range availableHops {
var key string
if len(hop.Nodes) > 0 && hop.Nodes[0].IP != nil {
key = hop.Nodes[0].IP.String()
} else {
key = "*" // 超时或无响应
}
if _, exists := hopCount[key]; !exists {
hopCount[key] = make([]*Hop, 0)
}
hopCount[key] = append(hopCount[key], hop)
}
// 按多数原则选择hop
if len(hopCount) == 1 {
// 所有hop都相同选择第一个
mergedHops = append(mergedHops, availableHops[0])
} else {
// 找出最多的hop类型
maxCount := 0
var majorityHops []*Hop
for _, hops := range hopCount {
if len(hops) > maxCount {
maxCount = len(hops)
majorityHops = hops
}
}
// 如果有多数派,使用多数派的第一个
if maxCount > 1 || len(hopCount) == 2 {
mergedHops = append(mergedHops, majorityHops[0])
} else {
// 三个都不同,按请求早晚顺序选择第一个
mergedHops = append(mergedHops, availableHops[0])
}
}
}
return mergedHops
}

5
bk/version.go Normal file
View File

@ -0,0 +1,5 @@
package backtrace
const BackTraceVersion = "v0.0.4"
var EnableLoger = false

View File

@ -6,14 +6,8 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"os" "os"
"runtime"
"sync"
"time"
"github.com/oneclickvirt/backtrace/bgptools"
backtrace "github.com/oneclickvirt/backtrace/bk" backtrace "github.com/oneclickvirt/backtrace/bk"
"github.com/oneclickvirt/backtrace/model"
"github.com/oneclickvirt/backtrace/utils"
. "github.com/oneclickvirt/defaultset" . "github.com/oneclickvirt/defaultset"
) )
@ -25,27 +19,17 @@ type IpInfo struct {
Org string `json:"org"` Org string `json:"org"`
} }
type ConcurrentResults struct {
bgpResult string
backtraceResult string
bgpError error
// backtraceError error
}
func main() { func main() {
go func() { go func() {
http.Get("https://hits.spiritlhl.net/backtrace.svg?action=hit&title=Hits&title_bg=%23555555&count_bg=%230eecf8&edge_flat=false") http.Get("https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Foneclickvirt%2Fbacktrace&count_bg=%2323E01C&title_bg=%23555555&icon=sonarcloud.svg&icon_color=%23E7E7E7&title=hits&edge_flat=false")
}() }()
fmt.Println(Green("Repo:"), Yellow("https://github.com/oneclickvirt/backtrace")) fmt.Println(Green("项目地址:"), Yellow("https://github.com/oneclickvirt/backtrace"))
var showVersion, showIpInfo, help, ipv6 bool var showVersion, showIpInfo, help bool
var specifiedIP string
backtraceFlag := flag.NewFlagSet("backtrace", flag.ContinueOnError) backtraceFlag := flag.NewFlagSet("backtrace", flag.ContinueOnError)
backtraceFlag.BoolVar(&help, "h", false, "Show help information") backtraceFlag.BoolVar(&help, "h", false, "Show help information")
backtraceFlag.BoolVar(&showVersion, "v", false, "Show version") backtraceFlag.BoolVar(&showVersion, "v", false, "Show version")
backtraceFlag.BoolVar(&showIpInfo, "s", true, "Disabe show ip info") backtraceFlag.BoolVar(&showIpInfo, "s", true, "Disabe show ip info")
backtraceFlag.BoolVar(&model.EnableLoger, "log", false, "Enable logging") backtraceFlag.BoolVar(&backtrace.EnableLoger, "e", false, "Enable logging")
backtraceFlag.BoolVar(&ipv6, "ipv6", false, "Enable ipv6 testing")
backtraceFlag.StringVar(&specifiedIP, "ip", "", "Specify IP address for bgptools")
backtraceFlag.Parse(os.Args[1:]) backtraceFlag.Parse(os.Args[1:])
if help { if help {
fmt.Printf("Usage: %s [options]\n", os.Args[0]) fmt.Printf("Usage: %s [options]\n", os.Args[0])
@ -53,91 +37,25 @@ func main() {
return return
} }
if showVersion { if showVersion {
fmt.Println(model.BackTraceVersion) fmt.Println(backtrace.BackTraceVersion)
return return
} }
info := IpInfo{}
if showIpInfo { if showIpInfo {
rsp, err := http.Get("http://ipinfo.io") rsp, err := http.Get("http://ipinfo.io")
if err != nil { if err != nil {
fmt.Printf("get ip info err %v \n", err.Error()) fmt.Errorf("Get ip info err %v \n", err.Error())
} else { } else {
info := IpInfo{}
err = json.NewDecoder(rsp.Body).Decode(&info) err = json.NewDecoder(rsp.Body).Decode(&info)
if err != nil { if err != nil {
fmt.Printf("json decode err %v \n", err.Error()) fmt.Errorf("json decode err %v \n", err.Error())
} else { } else {
fmt.Println(Green("国家: ") + White(info.Country) + Green(" 城市: ") + White(info.City) + fmt.Println(Green("国家: ") + White(info.Country) + Green(" 城市: ") + White(info.City) +
Green(" 服务商: ") + Blue(info.Org)) Green(" 服务商: ") + Blue(info.Org))
} }
} }
} }
preCheck := utils.CheckPublicAccess(3 * time.Second) backtrace.BackTrace()
if !preCheck.Connected {
fmt.Println(Red("PreCheck IP Type Failed"))
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
fmt.Println("Press Enter to exit...")
fmt.Scanln()
}
return
}
var useIPv6 bool
switch preCheck.StackType {
case "DualStack":
useIPv6 = ipv6
case "IPv4":
useIPv6 = false
case "IPv6":
useIPv6 = true
default:
fmt.Println(Red("PreCheck IP Type Failed"))
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
fmt.Println("Press Enter to exit...")
fmt.Scanln()
}
return
}
results := ConcurrentResults{}
var wg sync.WaitGroup
var targetIP string
if specifiedIP != "" {
targetIP = specifiedIP
} else if info.Ip != "" {
targetIP = info.Ip
}
if targetIP != "" {
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < 2; i++ {
result, err := bgptools.GetPoPInfo(targetIP)
results.bgpError = err
if err == nil && result.Result != "" {
results.bgpResult = result.Result
return
}
if i == 0 {
time.Sleep(3 * time.Second)
}
}
}()
}
wg.Add(1)
go func() {
defer wg.Done()
result := backtrace.BackTrace(useIPv6)
results.backtraceResult = result
}()
wg.Wait()
if results.bgpResult != "" {
fmt.Print(results.bgpResult)
}
if results.backtraceResult != "" {
fmt.Printf("%s\n", results.backtraceResult)
}
fmt.Println(Yellow("准确线路自行查看详细路由,本测试结果仅作参考")) fmt.Println(Yellow("准确线路自行查看详细路由,本测试结果仅作参考"))
fmt.Println(Yellow("同一目标地址多个线路时,检测可能已越过汇聚层,除第一个线路外,后续信息可能无效")) fmt.Println(Yellow("同一目标地址多个线路时,可能检测已越过汇聚层,除了第一个线路外,后续信息可能无效"))
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
fmt.Println("Press Enter to exit...")
fmt.Scanln()
}
} }

23
go.mod
View File

@ -1,31 +1,14 @@
module github.com/oneclickvirt/backtrace module github.com/oneclickvirt/backtrace
go 1.25.3 go 1.22.4
require ( require (
github.com/google/uuid v1.6.0
github.com/imroc/req/v3 v3.54.0
github.com/oneclickvirt/defaultset v0.0.0-20240624051018-30a50859e1b5 github.com/oneclickvirt/defaultset v0.0.0-20240624051018-30a50859e1b5
golang.org/x/net v0.41.0 golang.org/x/net v0.26.0
golang.org/x/sys v0.33.0 golang.org/x/sys v0.21.0
) )
require ( require (
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/icholy/digest v1.1.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/quic-go/quic-go v0.53.0 // indirect
github.com/refraction-networking/utls v1.7.3 // indirect
go.uber.org/mock v0.5.2 // indirect
go.uber.org/multierr v1.10.0 // indirect go.uber.org/multierr v1.10.0 // indirect
go.uber.org/zap v1.27.0 // indirect go.uber.org/zap v1.27.0 // indirect
golang.org/x/crypto v0.39.0 // indirect
golang.org/x/mod v0.25.0 // indirect
golang.org/x/sync v0.15.0 // indirect
golang.org/x/text v0.26.0 // indirect
golang.org/x/tools v0.34.0 // indirect
) )

53
go.sum
View File

@ -1,61 +1,20 @@
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/icholy/digest v1.1.0 h1:HfGg9Irj7i+IX1o1QAmPfIBNu/Q5A5Tu3n/MED9k9H4=
github.com/icholy/digest v1.1.0/go.mod h1:QNrsSGQ5v7v9cReDI0+eyjsXGUoRSUZQHeQ5C4XLa0Y=
github.com/imroc/req/v3 v3.54.0 h1:kwWJSpT7OvjJ/Q8ykp+69Ye5H486RKDcgEoepw1Ren4=
github.com/imroc/req/v3 v3.54.0/go.mod h1:P8gCJjG/XNUFeP6WOi40VAXfYwT+uPM00xvoBWiwzUQ=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/oneclickvirt/defaultset v0.0.0-20240624051018-30a50859e1b5 h1:TUM6XzOB7Z7OxyXi3fwlZY9KfuVbvUBusYiNbSfX208= 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/oneclickvirt/defaultset v0.0.0-20240624051018-30a50859e1b5/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/quic-go/quic-go v0.53.0 h1:QHX46sISpG2S03dPeZBgVIZp8dGagIaiu2FiVYvpCZI=
github.com/quic-go/quic-go v0.53.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
github.com/refraction-networking/utls v1.7.3 h1:L0WRhHY7Oq1T0zkdzVZMR6zWZv+sXbHB9zcuvsAEqCo=
github.com/refraction-networking/utls v1.7.3/go.mod h1:TUhh27RHMGtQvjQq+RyO11P6ZNQNBb3N0v7wsEjKAIQ=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 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/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.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= 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/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 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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=

View File

@ -1,158 +0,0 @@
package model
import "time"
const BackTraceVersion = "v0.0.8"
var EnableLoger = false
// IcmpTarget 定义ICMP目标的JSON结构
type IcmpTarget struct {
Province string `json:"province"`
ISP string `json:"isp"`
IPVersion string `json:"ip_version"`
IPs string `json:"ips"` // IP列表以逗号分隔
}
var (
IcmpTargets = "https://raw.githubusercontent.com/spiritLHLS/icmp_targets/main/nodes.json"
CdnList = []string{
"https://cdn.spiritlhl.net/",
"http://cdn1.spiritlhl.net/",
"http://cdn2.spiritlhl.net/",
"http://cdn3.spiritlhl.net/",
"http://cdn4.spiritlhl.net/",
}
Ipv4s = []string{
"219.141.140.10", // 北京电信v4
"202.106.195.68", // 北京联通v4
"221.179.155.161", // 北京移动v4
"202.96.209.133", // 上海电信v4
"210.22.97.1", // 上海联通v4
"211.136.112.200", // 上海移动v4
"58.60.188.222", // 广州电信v4
"210.21.196.6", // 广州联通v4
"120.196.165.24", // 广州移动v4
"61.139.2.69", // 成都电信v4
"119.6.6.6", // 成都联通v4
"211.137.96.205", // 成都移动v4
}
Ipv4Names = []string{
"北京电信v4", "北京联通v4", "北京移动v4",
"上海电信v4", "上海联通v4", "上海移动v4",
"广州电信v4", "广州联通v4", "广州移动v4",
"成都电信v4", "成都联通v4", "成都移动v4",
}
Ipv6s = []string{
"2400:89c0:1053:3::69", // 北京电信 IPv6
"2400:89c0:1013:3::54", // 北京联通 IPv6
"2409:8c00:8421:1303::55", // 北京移动 IPv6
"240e:e1:aa00:4000::24", // 上海电信 IPV6
"2408:80f1:21:5003::a", // 上海联通 IPv6
"2409:8c1e:75b0:3003::26", // 上海移动 IPv6
"240e:97c:2f:3000::44", // 广州电信 IPv6
"2408:8756:f50:1001::c", // 广州联通 IPv6
"2409:8c54:871:1001::12", // 广州移动 IPv6
}
Ipv6Names = []string{
"北京电信v6", "北京联通v6", "北京移动v6",
"上海电信v6", "上海联通v6", "上海移动v6",
"广州电信v6", "广州联通v6", "广州移动v6",
}
M = map[string]string{
// [] 前的字符串个数中文占2个字符串
"AS23764": "电信CTGNET [精品线路]",
"AS4809a": "电信CN2GIA [精品线路]",
"AS4809b": "电信CN2GT [优质线路]",
"AS4809": "电信CN2 [优质线路]",
"AS4134": "电信163 [普通线路]",
"AS9929": "联通9929 [优质线路]",
"AS4837": "联通4837 [普通线路]",
"AS58807": "移动CMIN2 [精品线路]",
"AS9808": "移动CMI [普通线路]",
"AS58453": "移动CMI [普通线路]",
}
CachedIcmpData string
CachedIcmpDataFetchTime time.Time
ParsedIcmpTargets []IcmpTarget
)
var Tier1Global = map[string]string{
"174": "Cogent",
"1299": "Arelion",
"3356": "Lumen",
"3257": "GTT",
"7018": "AT&T",
"701": "Verizon",
"2914": "NTT",
"6453": "Tata",
"3320": "DTAG",
"5511": "Orange",
"3491": "PCCW",
"6461": "Zayo",
"6830": "Liberty",
"6762": "Sparkle",
"12956": "Telxius",
"702": "Verizon",
}
var Tier1Regional = map[string]string{
"4134": "ChinaNet",
"4837": "China Unicom",
"9808": "China Mobile",
"4766": "Korea Telecom",
"2516": "KDDI",
"7713": "Telkomnet",
"9121": "Etisalat",
"7473": "SingTel",
"4637": "Telstra",
"5400": "British Telecom",
"2497": "IIJ",
"3462": "Chunghwa Telecom",
"3463": "TWNIC",
"12389": "SoftBank",
"3303": "MTS",
"45609": "Reliance Jio",
}
var Tier2 = map[string]string{
"6939": "HurricaneElectric",
"20485": "Transtelecom",
"1273": "Vodafone",
"1239": "Sprint",
"6453": "Tata",
"6762": "Sparkle",
"9002": "RETN",
"7922": "Comcast",
"23754": "Rostelecom",
"3320": "DTAG",
}
var ContentProviders = map[string]string{
"15169": "Google",
"32934": "Facebook",
"54113": "Fastly",
"20940": "Akamai",
"13335": "Cloudflare",
"14618": "Amazon AWS",
"55102": "Netflix CDN",
"4685": "CacheFly",
"16509": "Amazon",
"36040": "Amazon CloudFront",
"36459": "EdgeCast",
"24940": "CDNetworks",
}
var IXPS = map[string]string{
"5539": "IX.br",
"25291": "HKIX",
"1200": "AMS-IX",
"6695": "DE-CIX",
"58558": "LINX",
"395848": "France-IX",
"4713": "JPNAP",
"4635": "SIX",
"2906": "MSK-IX",
"1273": "NIX.CZ",
}

View File

@ -1,160 +0,0 @@
package utils
import (
"context"
"net"
"net/http"
"sync"
"time"
)
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)
},
}
}
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,
}
}

View File

@ -1,17 +0,0 @@
package utils
import (
"fmt"
"testing"
"time"
)
func TestCheckPublicAccess(t *testing.T) {
timeout := 3 * time.Second
result := CheckPublicAccess(timeout)
if result.Connected {
fmt.Printf("✅ 本机有公网连接,类型: %s\n", result.StackType)
} else {
fmt.Println("❌ 本机未检测到公网连接")
}
}