mirror of
https://github.com/oneclickvirt/backtrace.git
synced 2025-09-18 08:17:08 +08:00
Compare commits
No commits in common. "main" and "v0.0.5-20250411151652" have entirely different histories.
main
...
v0.0.5-202
28
.github/workflows/ci.yaml
vendored
28
.github/workflows/ci.yaml
vendored
@ -7,45 +7,25 @@ jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
go: ['1.22.x']
|
||||
os: [ubuntu-latest]
|
||||
go: [ '1.22.x' ]
|
||||
os: [ ubuntu-latest ]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
|
||||
- name: Test
|
||||
run: go test ./... -coverprofile=coverage.txt
|
||||
|
||||
- name: Create Tag
|
||||
if: success()
|
||||
if: success() # 仅在测试成功时运行
|
||||
run: |
|
||||
git config --global user.name 'github-actions'
|
||||
git config --global user.email 'github-actions@github.com'
|
||||
TAG="v0.0.7-$(date +'%Y%m%d%H%M%S')"
|
||||
TAG="v0.0.5-$(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 }}
|
7
.github/workflows/main.yaml
vendored
7
.github/workflows/main.yaml
vendored
@ -55,12 +55,7 @@ jobs:
|
||||
run: |
|
||||
mkdir -p bin
|
||||
cd cmd
|
||||
CGO_ENABLED=0 GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} go build \
|
||||
-o ../bin/backtrace-${{ matrix.goos }}-${{ matrix.goarch }} \
|
||||
-v \
|
||||
-ldflags="-extldflags=-static -s -w" \
|
||||
-trimpath \
|
||||
.
|
||||
CGO_ENABLED=0 GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} go build -o ../bin/backtrace-${{ matrix.goos }}-${{ matrix.goarch }} -v -ldflags="-extldflags=-static" .
|
||||
|
||||
- name: Upload New Assets
|
||||
run: |
|
||||
|
13
README.md
13
README.md
@ -1,6 +1,6 @@
|
||||
# backtrace
|
||||
|
||||
[](https://hits.spiritlhl.net) [](https://github.com/oneclickvirt/backtrace/releases)
|
||||
[](https://hits.spiritlhl.net)
|
||||
|
||||
[](https://github.com/oneclickvirt/backtrace/actions/workflows/main.yaml)
|
||||
|
||||
@ -15,12 +15,13 @@
|
||||
- [x] 支持对```CTGNET```、```CN2GIA```和```CN2GT```线路的判断
|
||||
- [x] 支持对```CMIN2```和```CMI```线路的判断
|
||||
- [x] 支持对整个回程路由进行线路分析,一个目标IP可能会分析出多种线路
|
||||
- [x] 支持对主流接入点的线路检测,方便分析国际互联能力
|
||||
- [x] 多次并发路由检测以分析平均多次路由,避免单次路由因网络波动未能准确检测
|
||||
- [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
|
||||
|
||||
- [ ] 自动检测汇聚层,裁剪结果不输出汇聚层后的线路(区分境内外段)
|
||||
- [ ] 添加对主流ISP的POP点检测,区分国际互联能力
|
||||
|
||||
## 使用
|
||||
|
||||
@ -55,8 +56,6 @@ backtrace
|
||||
```
|
||||
Usage: backtrace [options]
|
||||
-h Show help information
|
||||
-ip string
|
||||
Specify IP address for bgptools
|
||||
-ipv6
|
||||
Enable ipv6 testing
|
||||
-log
|
||||
@ -75,7 +74,7 @@ rm -rf /usr/bin/backtrace
|
||||
## 在Golang中使用
|
||||
|
||||
```
|
||||
go get github.com/oneclickvirt/backtrace@v0.0.7-20250811023541
|
||||
go get github.com/oneclickvirt/backtrace@latest
|
||||
```
|
||||
|
||||
## 概览图
|
||||
|
403
bgptools/pop.go
403
bgptools/pop.go
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
package backtrace
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/oneclickvirt/backtrace/model"
|
||||
)
|
||||
|
||||
func BackTrace(enableIpv6 bool) string {
|
||||
func BackTrace(enableIpv6 bool) {
|
||||
if model.CachedIcmpData == "" || model.ParsedIcmpTargets == nil || time.Since(model.CachedIcmpDataFetchTime) > time.Hour {
|
||||
model.CachedIcmpData = getData(model.IcmpTargets)
|
||||
model.CachedIcmpDataFetchTime = time.Now()
|
||||
@ -15,7 +15,6 @@ func BackTrace(enableIpv6 bool) string {
|
||||
model.ParsedIcmpTargets = parseIcmpTargets(model.CachedIcmpData)
|
||||
}
|
||||
}
|
||||
var builder strings.Builder
|
||||
if enableIpv6 {
|
||||
ipv4Count := len(model.Ipv4s)
|
||||
ipv6Count := len(model.Ipv6s)
|
||||
@ -40,18 +39,14 @@ func BackTrace(enableIpv6 bool) string {
|
||||
break loopIPv4v6
|
||||
}
|
||||
}
|
||||
// 收集 IPv4 结果
|
||||
for i := 0; i < ipv4Count; i++ {
|
||||
if s[i] != "" {
|
||||
builder.WriteString(s[i])
|
||||
builder.WriteString("\n")
|
||||
fmt.Println(s[i])
|
||||
}
|
||||
}
|
||||
// 收集 IPv6 结果
|
||||
for i := ipv4Count; i < totalCount; i++ {
|
||||
if s[i] != "" {
|
||||
builder.WriteString(s[i])
|
||||
builder.WriteString("\n")
|
||||
fmt.Println(s[i])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -73,15 +68,10 @@ func BackTrace(enableIpv6 bool) string {
|
||||
break loopIPv4
|
||||
}
|
||||
}
|
||||
// 收集结果
|
||||
for _, r := range s {
|
||||
if r != "" {
|
||||
builder.WriteString(r)
|
||||
builder.WriteString("\n")
|
||||
fmt.Println(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 返回完整结果,去掉末尾的换行符
|
||||
result := builder.String()
|
||||
return strings.TrimSuffix(result, "\n")
|
||||
}
|
||||
|
@ -17,10 +17,8 @@
|
||||
2a04:f581:110b
|
||||
2a04:f581:a123
|
||||
2a04:f581:a125
|
||||
2a0f:7806:fffc
|
||||
2c0f:f7a8:1
|
||||
2c0f:f7a8:2
|
||||
2c0f:f7a8:24
|
||||
2c0f:f7a8:29
|
||||
2c0f:f7a8:37
|
||||
2c0f:f7a8:47
|
||||
2c0f:f7a8:9011
|
||||
|
@ -1,9 +1,11 @@
|
||||
2400:9380:8001
|
||||
2400:9380:8003
|
||||
2400:9380:8021
|
||||
2400:9380:8030
|
||||
2400:9380:8040
|
||||
2400:9380:8140
|
||||
2400:9380:8201
|
||||
2400:9380:8221
|
||||
2400:9380:8301
|
||||
240e
|
||||
240e:1
|
||||
@ -64,6 +66,7 @@
|
||||
240e:426
|
||||
240e:427
|
||||
240e:42:4000
|
||||
240e:42:8010
|
||||
240e:43
|
||||
240e:43:8000
|
||||
240e:44
|
||||
@ -76,6 +79,7 @@
|
||||
240e:446
|
||||
240e:447
|
||||
240e:45
|
||||
240e:45:8000
|
||||
240e:46
|
||||
240e:46:5008
|
||||
240e:47
|
||||
|
@ -1,3 +1,4 @@
|
||||
2400:9380:9000
|
||||
2400:9380:9001
|
||||
2400:9380:9002
|
||||
2400:9380:9005
|
||||
@ -5,14 +6,20 @@
|
||||
2400:9380:900a
|
||||
2400:9380:9020
|
||||
2400:9380:9021
|
||||
2400:9380:9030
|
||||
2400:9380:9031
|
||||
2400:9380:9040
|
||||
2400:9380:9041
|
||||
2400:9380:9050
|
||||
2400:9380:9051
|
||||
2400:9380:9060
|
||||
2400:9380:9061
|
||||
2400:9380:9070
|
||||
2400:9380:9071
|
||||
2400:9380:9080
|
||||
2400:9380:9081
|
||||
2400:9380:90a0
|
||||
2400:9380:90a1
|
||||
2400:9380:90b0
|
||||
2400:9380:90b1
|
||||
2400:9380:90b2
|
||||
@ -26,18 +33,26 @@
|
||||
2400:9380:9121
|
||||
2400:9380:9201
|
||||
2400:9380:9202
|
||||
2400:9380:9205
|
||||
2400:9380:9220
|
||||
2400:9380:9221
|
||||
2400:9380:9230
|
||||
2400:9380:9231
|
||||
2400:9380:9240
|
||||
2400:9380:9241
|
||||
2400:9380:9250
|
||||
2400:9380:9251
|
||||
2400:9380:9252
|
||||
2400:9380:9260
|
||||
2400:9380:9261
|
||||
2400:9380:9270
|
||||
2400:9380:9271
|
||||
2400:9380:9272
|
||||
2400:9380:9280
|
||||
2400:9380:9281
|
||||
2400:9380:9282
|
||||
2400:9380:92a0
|
||||
2400:9380:92a1
|
||||
2400:9380:92b0
|
||||
2400:9380:92b1
|
||||
2400:9380:92b2
|
||||
@ -76,11 +91,9 @@
|
||||
240e:476:febf
|
||||
240e:476:feff
|
||||
240e:604:314
|
||||
240e:604:319
|
||||
240e:60e:8000
|
||||
240e:60e:8001
|
||||
240e:615
|
||||
240e:61d
|
||||
240e:62c
|
||||
240e:638:f
|
||||
240e:63c
|
||||
@ -97,19 +110,22 @@
|
||||
240e:713:f020
|
||||
240e:713:f021
|
||||
240e:733:4c0
|
||||
240e:7af
|
||||
240e:f6:8002
|
||||
240e:f6:8003
|
||||
2605:9d80:9003
|
||||
2605:9d80:9013
|
||||
2605:9d80:9023
|
||||
2605:9d80:9033
|
||||
2605:9d80:9042
|
||||
2605:9d80:9052
|
||||
2605:9d80:9061
|
||||
2605:9d80:9071
|
||||
2605:9d80:9092
|
||||
2804:1e48
|
||||
2804:1e48:9001
|
||||
2804:1e48:9002
|
||||
2a04:f580:9000
|
||||
2a04:f580:9001
|
||||
2a04:f580:9002
|
||||
2a04:f580:9010
|
||||
2a04:f580:9012
|
||||
2a04:f580:9013
|
||||
@ -121,6 +137,9 @@
|
||||
2a04:f580:9070
|
||||
2a04:f580:9080
|
||||
2a04:f580:9090
|
||||
2a04:f580:9200
|
||||
2a04:f580:9201
|
||||
2a04:f580:9202
|
||||
2a04:f580:9210
|
||||
2a04:f580:9212
|
||||
2a04:f580:9213
|
||||
@ -133,6 +152,7 @@
|
||||
2a04:f580:9280
|
||||
2a04:f580:9290
|
||||
2c0f:f7a8:9010
|
||||
2c0f:f7a8:9011
|
||||
2c0f:f7a8:9020
|
||||
2c0f:f7a8:9041
|
||||
2c0f:f7a8:9210
|
||||
|
@ -1,9 +1,11 @@
|
||||
2400:73e0:201
|
||||
2402:f140:ff13
|
||||
2402:f140:ff14
|
||||
2404:6500:dcb3
|
||||
2405:1480
|
||||
2406:1e40
|
||||
2406:cac0
|
||||
2407:37c0:ffff
|
||||
2407:6c40
|
||||
2407:6c40:1500
|
||||
2408
|
||||
@ -15,11 +17,13 @@
|
||||
2408:8001
|
||||
2408:802a
|
||||
2408:802c
|
||||
2408:8034
|
||||
2408:803e
|
||||
2408:8056
|
||||
2408:80c2
|
||||
2408:80c5
|
||||
2408:80e2
|
||||
2408:80e3
|
||||
2408:80e9
|
||||
2408:80f5
|
||||
2408:80f9
|
||||
@ -69,9 +73,9 @@
|
||||
2408:8252
|
||||
2408:8253
|
||||
2408:8254
|
||||
2408:8255
|
||||
2408:825c
|
||||
2408:825d
|
||||
2408:825f
|
||||
2408:8260
|
||||
2408:8262
|
||||
2408:8263
|
||||
@ -115,6 +119,7 @@
|
||||
2408:8352
|
||||
2408:8353
|
||||
2408:8354
|
||||
2408:8355
|
||||
2408:8360
|
||||
2408:8361
|
||||
2408:8362
|
||||
@ -234,7 +239,6 @@
|
||||
2408:8612
|
||||
2408:8613
|
||||
2408:8614
|
||||
2408:8614:e20
|
||||
2408:8615
|
||||
2408:861a
|
||||
2408:861b
|
||||
@ -272,9 +276,9 @@
|
||||
2408:8651
|
||||
2408:8652
|
||||
2408:8653
|
||||
2408:8654
|
||||
2408:865c
|
||||
2408:865d
|
||||
2408:865f
|
||||
2408:8660
|
||||
2408:8662
|
||||
2408:8663
|
||||
@ -284,7 +288,6 @@
|
||||
2408:866a
|
||||
2408:866b
|
||||
2408:866c
|
||||
2408:866c:202
|
||||
2408:866d
|
||||
2408:866e
|
||||
2408:866f
|
||||
@ -357,6 +360,8 @@
|
||||
2408:8813
|
||||
2408:8814
|
||||
2408:8815
|
||||
2408:8816
|
||||
2408:8817
|
||||
2408:8818
|
||||
2408:8819
|
||||
2408:882c
|
||||
|
@ -1,7 +1,3 @@
|
||||
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
|
||||
@ -9,11 +5,3 @@
|
||||
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
|
||||
|
@ -32,11 +32,6 @@
|
||||
2409:8004:3820
|
||||
2409:8004:3821
|
||||
2409:8004:3822
|
||||
2409:8004:3840
|
||||
2409:8004:3841
|
||||
2409:8004:3842
|
||||
2409:8004:3843
|
||||
2409:8004:3844
|
||||
2409:8004:38c0
|
||||
2409:8004:801
|
||||
2409:8004:807
|
||||
@ -63,7 +58,6 @@
|
||||
2409:800b:2905
|
||||
2409:800b:2906
|
||||
2409:800b:2908
|
||||
2409:800b:290e
|
||||
2409:800b:290f
|
||||
2409:800b:2b03
|
||||
2409:800b:2b05
|
||||
@ -79,7 +73,6 @@
|
||||
2409:8013:2905
|
||||
2409:8013:2907
|
||||
2409:8013:2908
|
||||
2409:8013:290a
|
||||
2409:8013:2b01
|
||||
2409:8013:2b02
|
||||
2409:8013:2b04
|
||||
@ -93,13 +86,13 @@
|
||||
2409:8017:2903
|
||||
2409:8017:2904
|
||||
2409:8017:2905
|
||||
2409:8017:2907
|
||||
2409:8017:2b01
|
||||
2409:8017:2b03
|
||||
2409:8017:2b05
|
||||
2409:8017:2b06
|
||||
2409:8017:2c01
|
||||
2409:8017:2c03
|
||||
2409:8018:28f0
|
||||
2409:8018:28f1
|
||||
2409:801a:3802
|
||||
2409:801d:2901
|
||||
@ -159,7 +152,6 @@
|
||||
2409:802e:2905
|
||||
2409:802e:2906
|
||||
2409:802e:2909
|
||||
2409:802e:290d
|
||||
2409:802e:2a05
|
||||
2409:802e:2a06
|
||||
2409:802e:2b01
|
||||
@ -188,7 +180,6 @@
|
||||
2409:802f:290b
|
||||
2409:802f:290e
|
||||
2409:802f:290f
|
||||
2409:802f:2915
|
||||
2409:802f:2a05
|
||||
2409:802f:2a0b
|
||||
2409:802f:2b05
|
||||
@ -241,11 +232,8 @@
|
||||
2409:8031
|
||||
2409:8034
|
||||
2409:8034:1
|
||||
2409:8034:822
|
||||
2409:8038
|
||||
2409:8038:3005
|
||||
2409:8038:3007
|
||||
2409:8038:3008
|
||||
2409:8038:3950
|
||||
2409:8038:3970
|
||||
2409:803c:3006
|
||||
@ -254,7 +242,6 @@
|
||||
2409:803c:300b
|
||||
2409:803c:3090
|
||||
2409:803c:3098
|
||||
2409:803c:30a0
|
||||
2409:803c:30b0
|
||||
2409:803c:30c0
|
||||
2409:8043:2901
|
||||
@ -263,7 +250,6 @@
|
||||
2409:8043:2905
|
||||
2409:8043:2907
|
||||
2409:8043:2908
|
||||
2409:8043:290b
|
||||
2409:8043:290c
|
||||
2409:8043:2b01
|
||||
2409:8043:2b02
|
||||
@ -282,20 +268,15 @@
|
||||
2409:804b:290a
|
||||
2409:804b:290b
|
||||
2409:804b:290c
|
||||
2409:804b:2910
|
||||
2409:804b:2911
|
||||
2409:804b:29ff
|
||||
2409:804b:2b05
|
||||
2409:804b:2b06
|
||||
2409:804b:2b07
|
||||
2409:804b:2b08
|
||||
2409:804b:2b0b
|
||||
2409:804b:2b10
|
||||
2409:804b:2b11
|
||||
2409:804b:2c06
|
||||
2409:804b:2c0b
|
||||
2409:804b:800
|
||||
2409:804b:801
|
||||
2409:804c
|
||||
2409:804c:10
|
||||
2409:804c:11
|
||||
@ -322,14 +303,6 @@
|
||||
2409:804d
|
||||
2409:804e
|
||||
2409:804f
|
||||
2409:8053:2909
|
||||
2409:8053:290a
|
||||
2409:8053:2b09
|
||||
2409:8053:2b0a
|
||||
2409:8053:807
|
||||
2409:8053:808
|
||||
2409:8053:809
|
||||
2409:8053:80a
|
||||
2409:8054:10
|
||||
2409:8054:18
|
||||
2409:8054:20
|
||||
@ -419,9 +392,6 @@
|
||||
2409:8057:3817
|
||||
2409:8057:3818
|
||||
2409:8057:381a
|
||||
2409:8057:381b
|
||||
2409:8057:381d
|
||||
2409:8057:381e
|
||||
2409:8057:3820
|
||||
2409:8057:800
|
||||
2409:8057:804
|
||||
@ -489,15 +459,12 @@
|
||||
2409:8060:8eb
|
||||
2409:8061
|
||||
2409:8061:2806
|
||||
2409:8061:280a
|
||||
2409:8061:2900
|
||||
2409:8061:2901
|
||||
2409:8061:2902
|
||||
2409:8061:2904
|
||||
2409:8061:2905
|
||||
2409:8061:2906
|
||||
2409:8061:2907
|
||||
2409:8061:290a
|
||||
2409:8061:2a01
|
||||
2409:8061:2a02
|
||||
2409:8061:2a04
|
||||
@ -736,8 +703,6 @@
|
||||
2409:8087:8015
|
||||
2409:8087:8016
|
||||
2409:8087:8017
|
||||
2409:8087:8018
|
||||
2409:8087:8019
|
||||
2409:8087:8106
|
||||
2409:8089:1020
|
||||
2409:815c
|
||||
@ -769,56 +734,27 @@
|
||||
2409:8710
|
||||
2409:8713
|
||||
2409:8730
|
||||
2409:8730:110
|
||||
2409:8730:17f
|
||||
2409:8730:18f
|
||||
2409:8730:19c
|
||||
2409:8730:1af
|
||||
2409:8730:30
|
||||
2409:8730:50
|
||||
2409:8731
|
||||
2409:8731:1bf
|
||||
2409:8731:1cf
|
||||
2409:8731:1df
|
||||
2409:8731:21f
|
||||
2409:8731:22f
|
||||
2409:8731:37f
|
||||
2409:8734
|
||||
2409:8734:1801
|
||||
2409:8734:1861
|
||||
2409:8734:1871
|
||||
2409:8734:1e20
|
||||
2409:8734:2350
|
||||
2409:8734:2351
|
||||
2409:8734:2450
|
||||
2409:8734:2451
|
||||
2409:8734:c01
|
||||
2409:8734:c10
|
||||
2409:8738
|
||||
2409:8738:c20
|
||||
2409:8739:fffe
|
||||
2409:8739:ffff
|
||||
2409:874c
|
||||
2409:874c:c10
|
||||
2409:874d
|
||||
2409:874e
|
||||
2409:874f
|
||||
2409:8754
|
||||
2409:8754:211
|
||||
2409:8754:21e
|
||||
2409:8754:21f
|
||||
2409:8754:3250
|
||||
2409:8754:34b0
|
||||
2409:8754:34c0
|
||||
2409:8754:34d0
|
||||
2409:8754:34e0
|
||||
2409:8754:3e61
|
||||
2409:875c
|
||||
2409:875c:ff01
|
||||
2409:875c:ff02
|
||||
2409:875e
|
||||
2409:875e:a031
|
||||
2409:875e:a032
|
||||
2409:875f
|
||||
2409:8760
|
||||
2409:8760:ea00
|
||||
@ -828,8 +764,6 @@
|
||||
2409:8762:fd01
|
||||
2409:8762:fd02
|
||||
2409:876a
|
||||
2409:876a:a00
|
||||
2409:876a:a10
|
||||
2409:876a:fe00
|
||||
2409:876a:fe01
|
||||
2409:876b
|
||||
@ -845,201 +779,7 @@
|
||||
2409:877a
|
||||
2409:877c
|
||||
2409:877e
|
||||
2409:877e:1
|
||||
2409:877e:1201
|
||||
2409:877e:1202
|
||||
2409:877e:1203
|
||||
2409:877e:1204
|
||||
2409:877e:1205
|
||||
2409:877e:1206
|
||||
2409:877e:1207
|
||||
2409:877e:1501
|
||||
2409:877e:1502
|
||||
2409:877e:1503
|
||||
2409:877e:1504
|
||||
2409:877e:1505
|
||||
2409:877e:1506
|
||||
2409:877e:1507
|
||||
2409:877e:1801
|
||||
2409:877e:1802
|
||||
2409:877e:1b01
|
||||
2409:877e:1b02
|
||||
2409:877e:1e01
|
||||
2409:877e:1e02
|
||||
2409:877e:2
|
||||
2409:877e:2101
|
||||
2409:877e:2102
|
||||
2409:877e:2401
|
||||
2409:877e:2402
|
||||
2409:877e:2701
|
||||
2409:877e:2702
|
||||
2409:877e:2a01
|
||||
2409:877e:2a02
|
||||
2409:877e:2d01
|
||||
2409:877e:2d02
|
||||
2409:877e:3
|
||||
2409:877e:3001
|
||||
2409:877e:3002
|
||||
2409:877e:301
|
||||
2409:877e:302
|
||||
2409:877e:303
|
||||
2409:877e:304
|
||||
2409:877e:305
|
||||
2409:877e:306
|
||||
2409:877e:307
|
||||
2409:877e:3301
|
||||
2409:877e:3302
|
||||
2409:877e:3601
|
||||
2409:877e:3602
|
||||
2409:877e:3901
|
||||
2409:877e:3902
|
||||
2409:877e:3c01
|
||||
2409:877e:3c02
|
||||
2409:877e:3f01
|
||||
2409:877e:3f02
|
||||
2409:877e:4
|
||||
2409:877e:4201
|
||||
2409:877e:4202
|
||||
2409:877e:4501
|
||||
2409:877e:4502
|
||||
2409:877e:4801
|
||||
2409:877e:4802
|
||||
2409:877e:4b01
|
||||
2409:877e:4b02
|
||||
2409:877e:4e01
|
||||
2409:877e:4e02
|
||||
2409:877e:5
|
||||
2409:877e:5101
|
||||
2409:877e:5102
|
||||
2409:877e:5401
|
||||
2409:877e:5402
|
||||
2409:877e:5701
|
||||
2409:877e:5702
|
||||
2409:877e:5a01
|
||||
2409:877e:5a02
|
||||
2409:877e:5d01
|
||||
2409:877e:5d02
|
||||
2409:877e:6
|
||||
2409:877e:6001
|
||||
2409:877e:6002
|
||||
2409:877e:601
|
||||
2409:877e:602
|
||||
2409:877e:603
|
||||
2409:877e:604
|
||||
2409:877e:605
|
||||
2409:877e:606
|
||||
2409:877e:607
|
||||
2409:877e:6301
|
||||
2409:877e:6302
|
||||
2409:877e:6602
|
||||
2409:877e:6901
|
||||
2409:877e:6902
|
||||
2409:877e:6c01
|
||||
2409:877e:6c02
|
||||
2409:877e:6f01
|
||||
2409:877e:6f02
|
||||
2409:877e:7
|
||||
2409:877e:7201
|
||||
2409:877e:7202
|
||||
2409:877e:7501
|
||||
2409:877e:7502
|
||||
2409:877e:7801
|
||||
2409:877e:7802
|
||||
2409:877e:7b01
|
||||
2409:877e:7b02
|
||||
2409:877e:7e01
|
||||
2409:877e:7e02
|
||||
2409:877e:8101
|
||||
2409:877e:8102
|
||||
2409:877e:8401
|
||||
2409:877e:8402
|
||||
2409:877e:8701
|
||||
2409:877e:8702
|
||||
2409:877e:8a01
|
||||
2409:877e:8a02
|
||||
2409:877e:8d01
|
||||
2409:877e:8d02
|
||||
2409:877e:9001
|
||||
2409:877e:9002
|
||||
2409:877e:901
|
||||
2409:877e:902
|
||||
2409:877e:903
|
||||
2409:877e:904
|
||||
2409:877e:905
|
||||
2409:877e:906
|
||||
2409:877e:907
|
||||
2409:877e:9301
|
||||
2409:877e:9302
|
||||
2409:877e:9313
|
||||
2409:877e:9601
|
||||
2409:877e:9602
|
||||
2409:877e:9901
|
||||
2409:877e:9902
|
||||
2409:877e:9c01
|
||||
2409:877e:9c02
|
||||
2409:877e:9f01
|
||||
2409:877e:9f02
|
||||
2409:877e:a201
|
||||
2409:877e:a202
|
||||
2409:877e:a501
|
||||
2409:877e:a502
|
||||
2409:877e:a801
|
||||
2409:877e:a802
|
||||
2409:877e:ab01
|
||||
2409:877e:ab02
|
||||
2409:877e:ae01
|
||||
2409:877e:ae02
|
||||
2409:877e:b101
|
||||
2409:877e:b102
|
||||
2409:877e:b401
|
||||
2409:877e:b402
|
||||
2409:877e:b701
|
||||
2409:877e:b702
|
||||
2409:877e:ba01
|
||||
2409:877e:ba02
|
||||
2409:877e:bd01
|
||||
2409:877e:bd02
|
||||
2409:877e:c001
|
||||
2409:877e:c002
|
||||
2409:877e:c01
|
||||
2409:877e:c02
|
||||
2409:877e:c03
|
||||
2409:877e:c04
|
||||
2409:877e:c05
|
||||
2409:877e:c06
|
||||
2409:877e:c07
|
||||
2409:877e:c301
|
||||
2409:877e:c302
|
||||
2409:877e:c601
|
||||
2409:877e:c602
|
||||
2409:877e:c901
|
||||
2409:877e:c902
|
||||
2409:877e:cc01
|
||||
2409:877e:cc02
|
||||
2409:877e:cf01
|
||||
2409:877e:cf02
|
||||
2409:877e:d201
|
||||
2409:877e:d202
|
||||
2409:877e:d501
|
||||
2409:877e:d502
|
||||
2409:877e:d801
|
||||
2409:877e:d802
|
||||
2409:877e:db01
|
||||
2409:877e:db02
|
||||
2409:877e:de01
|
||||
2409:877e:de02
|
||||
2409:877e:f01
|
||||
2409:877e:f02
|
||||
2409:877e:f03
|
||||
2409:877e:f04
|
||||
2409:877e:f05
|
||||
2409:877e:f06
|
||||
2409:877e:f07
|
||||
2409:877f
|
||||
2409:877f:13
|
||||
2409:877f:14
|
||||
2409:877f:2
|
||||
2409:877f:5
|
||||
2409:8810
|
||||
2409:8830
|
||||
2409:8831
|
||||
@ -1078,15 +818,10 @@
|
||||
2409:8931:ff11
|
||||
2409:8931:ff12
|
||||
2409:8931:ff13
|
||||
2409:8931:ff21
|
||||
2409:8931:ff22
|
||||
2409:8931:ff23
|
||||
2409:8934
|
||||
2409:8938
|
||||
2409:8938:48c4
|
||||
2409:8938:48cc
|
||||
2409:8938:c0
|
||||
2409:8938:c4
|
||||
2409:894c
|
||||
2409:894d
|
||||
2409:894d:e78
|
||||
@ -1096,9 +831,7 @@
|
||||
2409:8958
|
||||
2409:8959
|
||||
2409:895a
|
||||
2409:895b
|
||||
2409:895c
|
||||
2409:895c:f900
|
||||
2409:895c:fc5a
|
||||
2409:895e
|
||||
2409:895e:ffa0
|
||||
@ -1240,17 +973,12 @@
|
||||
2409:8962:fcdc
|
||||
2409:8962:fcdd
|
||||
2409:8963
|
||||
2409:8964
|
||||
2409:896a
|
||||
2409:896a:1d4e
|
||||
2409:896a:1d7e
|
||||
2409:896a:fffd
|
||||
2409:896a:fffe
|
||||
2409:896b
|
||||
2409:896c
|
||||
2409:896d
|
||||
2409:896d:fff7
|
||||
2409:896d:fff8
|
||||
2409:896d:fff9
|
||||
2409:896d:fffa
|
||||
2409:896d:fffb
|
||||
@ -1262,8 +990,6 @@
|
||||
2409:8970
|
||||
2409:8970:135f
|
||||
2409:8970:137f
|
||||
2409:8970:992f
|
||||
2409:8970:996f
|
||||
2409:8974
|
||||
2409:8975
|
||||
2409:8978
|
||||
@ -1308,215 +1034,6 @@
|
||||
2409:8a7a
|
||||
2409:8a7c
|
||||
2409:8a7e
|
||||
2409:8a7e:1
|
||||
2409:8a7e:1201
|
||||
2409:8a7e:1202
|
||||
2409:8a7e:1203
|
||||
2409:8a7e:1204
|
||||
2409:8a7e:1205
|
||||
2409:8a7e:1206
|
||||
2409:8a7e:1207
|
||||
2409:8a7e:1208
|
||||
2409:8a7e:1501
|
||||
2409:8a7e:1502
|
||||
2409:8a7e:1503
|
||||
2409:8a7e:1504
|
||||
2409:8a7e:1505
|
||||
2409:8a7e:1506
|
||||
2409:8a7e:1507
|
||||
2409:8a7e:1508
|
||||
2409:8a7e:1801
|
||||
2409:8a7e:1802
|
||||
2409:8a7e:1803
|
||||
2409:8a7e:1804
|
||||
2409:8a7e:1b01
|
||||
2409:8a7e:1b02
|
||||
2409:8a7e:1e01
|
||||
2409:8a7e:1e02
|
||||
2409:8a7e:2
|
||||
2409:8a7e:2101
|
||||
2409:8a7e:2102
|
||||
2409:8a7e:2401
|
||||
2409:8a7e:2402
|
||||
2409:8a7e:2701
|
||||
2409:8a7e:2702
|
||||
2409:8a7e:2a01
|
||||
2409:8a7e:2a02
|
||||
2409:8a7e:2d01
|
||||
2409:8a7e:2d02
|
||||
2409:8a7e:3
|
||||
2409:8a7e:3001
|
||||
2409:8a7e:3002
|
||||
2409:8a7e:301
|
||||
2409:8a7e:302
|
||||
2409:8a7e:303
|
||||
2409:8a7e:304
|
||||
2409:8a7e:305
|
||||
2409:8a7e:306
|
||||
2409:8a7e:307
|
||||
2409:8a7e:308
|
||||
2409:8a7e:3301
|
||||
2409:8a7e:3302
|
||||
2409:8a7e:3601
|
||||
2409:8a7e:3602
|
||||
2409:8a7e:3901
|
||||
2409:8a7e:3902
|
||||
2409:8a7e:3903
|
||||
2409:8a7e:3904
|
||||
2409:8a7e:3c01
|
||||
2409:8a7e:3c02
|
||||
2409:8a7e:3f01
|
||||
2409:8a7e:3f02
|
||||
2409:8a7e:4
|
||||
2409:8a7e:4201
|
||||
2409:8a7e:4202
|
||||
2409:8a7e:4501
|
||||
2409:8a7e:4502
|
||||
2409:8a7e:4801
|
||||
2409:8a7e:4802
|
||||
2409:8a7e:4b01
|
||||
2409:8a7e:4b02
|
||||
2409:8a7e:4e01
|
||||
2409:8a7e:4e02
|
||||
2409:8a7e:5
|
||||
2409:8a7e:5101
|
||||
2409:8a7e:5102
|
||||
2409:8a7e:5401
|
||||
2409:8a7e:5402
|
||||
2409:8a7e:5701
|
||||
2409:8a7e:5702
|
||||
2409:8a7e:5a01
|
||||
2409:8a7e:5a02
|
||||
2409:8a7e:5d03
|
||||
2409:8a7e:5d04
|
||||
2409:8a7e:6
|
||||
2409:8a7e:6001
|
||||
2409:8a7e:6002
|
||||
2409:8a7e:601
|
||||
2409:8a7e:602
|
||||
2409:8a7e:603
|
||||
2409:8a7e:604
|
||||
2409:8a7e:605
|
||||
2409:8a7e:606
|
||||
2409:8a7e:607
|
||||
2409:8a7e:608
|
||||
2409:8a7e:6301
|
||||
2409:8a7e:6302
|
||||
2409:8a7e:6601
|
||||
2409:8a7e:6602
|
||||
2409:8a7e:6901
|
||||
2409:8a7e:6902
|
||||
2409:8a7e:6c01
|
||||
2409:8a7e:6c02
|
||||
2409:8a7e:6f01
|
||||
2409:8a7e:6f02
|
||||
2409:8a7e:7
|
||||
2409:8a7e:7201
|
||||
2409:8a7e:7202
|
||||
2409:8a7e:7501
|
||||
2409:8a7e:7502
|
||||
2409:8a7e:7801
|
||||
2409:8a7e:7802
|
||||
2409:8a7e:7b01
|
||||
2409:8a7e:7b02
|
||||
2409:8a7e:7e01
|
||||
2409:8a7e:7e02
|
||||
2409:8a7e:8
|
||||
2409:8a7e:8101
|
||||
2409:8a7e:8102
|
||||
2409:8a7e:8401
|
||||
2409:8a7e:8402
|
||||
2409:8a7e:8701
|
||||
2409:8a7e:8702
|
||||
2409:8a7e:8a01
|
||||
2409:8a7e:8a02
|
||||
2409:8a7e:8d01
|
||||
2409:8a7e:8d02
|
||||
2409:8a7e:9
|
||||
2409:8a7e:9001
|
||||
2409:8a7e:9002
|
||||
2409:8a7e:901
|
||||
2409:8a7e:902
|
||||
2409:8a7e:903
|
||||
2409:8a7e:904
|
||||
2409:8a7e:905
|
||||
2409:8a7e:906
|
||||
2409:8a7e:907
|
||||
2409:8a7e:908
|
||||
2409:8a7e:9301
|
||||
2409:8a7e:9302
|
||||
2409:8a7e:9303
|
||||
2409:8a7e:9304
|
||||
2409:8a7e:9601
|
||||
2409:8a7e:9602
|
||||
2409:8a7e:9901
|
||||
2409:8a7e:9902
|
||||
2409:8a7e:9c01
|
||||
2409:8a7e:9c02
|
||||
2409:8a7e:9f01
|
||||
2409:8a7e:9f02
|
||||
2409:8a7e:a201
|
||||
2409:8a7e:a202
|
||||
2409:8a7e:a501
|
||||
2409:8a7e:a502
|
||||
2409:8a7e:a801
|
||||
2409:8a7e:a802
|
||||
2409:8a7e:ab01
|
||||
2409:8a7e:ab02
|
||||
2409:8a7e:ae01
|
||||
2409:8a7e:ae02
|
||||
2409:8a7e:b101
|
||||
2409:8a7e:b102
|
||||
2409:8a7e:b401
|
||||
2409:8a7e:b402
|
||||
2409:8a7e:b403
|
||||
2409:8a7e:b404
|
||||
2409:8a7e:b701
|
||||
2409:8a7e:b702
|
||||
2409:8a7e:ba01
|
||||
2409:8a7e:ba02
|
||||
2409:8a7e:bd01
|
||||
2409:8a7e:bd02
|
||||
2409:8a7e:c001
|
||||
2409:8a7e:c002
|
||||
2409:8a7e:c01
|
||||
2409:8a7e:c02
|
||||
2409:8a7e:c04
|
||||
2409:8a7e:c05
|
||||
2409:8a7e:c06
|
||||
2409:8a7e:c07
|
||||
2409:8a7e:c08
|
||||
2409:8a7e:c301
|
||||
2409:8a7e:c302
|
||||
2409:8a7e:c601
|
||||
2409:8a7e:c602
|
||||
2409:8a7e:c901
|
||||
2409:8a7e:c902
|
||||
2409:8a7e:c903
|
||||
2409:8a7e:c905
|
||||
2409:8a7e:c906
|
||||
2409:8a7e:cc01
|
||||
2409:8a7e:cc02
|
||||
2409:8a7e:cf01
|
||||
2409:8a7e:cf02
|
||||
2409:8a7e:d201
|
||||
2409:8a7e:d202
|
||||
2409:8a7e:d501
|
||||
2409:8a7e:d502
|
||||
2409:8a7e:d801
|
||||
2409:8a7e:d802
|
||||
2409:8a7e:db01
|
||||
2409:8a7e:db02
|
||||
2409:8a7e:de01
|
||||
2409:8a7e:de02
|
||||
2409:8a7e:f01
|
||||
2409:8a7e:f02
|
||||
2409:8a7e:f03
|
||||
2409:8a7e:f04
|
||||
2409:8a7e:f05
|
||||
2409:8a7e:f06
|
||||
2409:8a7e:f07
|
||||
2409:8a7e:f08
|
||||
2409:8b10
|
||||
2409:8b30
|
||||
2409:8b34
|
||||
@ -1545,22 +1062,16 @@
|
||||
2409:8c00
|
||||
2409:8c00:7840
|
||||
2409:8c02
|
||||
2409:8c02:25c
|
||||
2409:8c10
|
||||
2409:8c14
|
||||
2409:8c15
|
||||
2409:8c1e
|
||||
2409:8c1e:3410
|
||||
2409:8c1e:68f0
|
||||
2409:8c1e:8f60
|
||||
2409:8c1f
|
||||
2409:8c20:1833
|
||||
2409:8c2f:3800
|
||||
2409:8c30
|
||||
2409:8c30:40
|
||||
2409:8c34
|
||||
2409:8c34:2000
|
||||
2409:8c34:600
|
||||
2409:8c38
|
||||
2409:8c38:1a00
|
||||
2409:8c39
|
||||
@ -1575,7 +1086,6 @@
|
||||
2409:8c3c:908
|
||||
2409:8c3c:909
|
||||
2409:8c3c:9fe
|
||||
2409:8c3c:a00
|
||||
2409:8c3c:ffff
|
||||
2409:8c44
|
||||
2409:8c44:1
|
||||
@ -1597,7 +1107,6 @@
|
||||
2409:8c4e
|
||||
2409:8c4f
|
||||
2409:8c54
|
||||
2409:8c54:813
|
||||
2409:8c5b:ffff
|
||||
2409:8c5c
|
||||
2409:8c5e
|
||||
@ -1606,7 +1115,6 @@
|
||||
2409:8c5e:a0
|
||||
2409:8c5f
|
||||
2409:8c60
|
||||
2409:8c60:2400
|
||||
2409:8c60:ea00
|
||||
2409:8c61
|
||||
2409:8c62
|
||||
@ -1635,17 +1143,14 @@
|
||||
2409:8c70:3ad4
|
||||
2409:8c70:3ad8
|
||||
2409:8c74
|
||||
2409:8c74:7e10
|
||||
2409:8c78
|
||||
2409:8c7a
|
||||
2409:8c7c
|
||||
2409:8c7e
|
||||
2409:8c7e:9301
|
||||
2409:8c85
|
||||
2409:8c85:1
|
||||
2409:8c85:1000
|
||||
2409:8c85:1001
|
||||
2409:8c85:1e00
|
||||
2409:8c85:2
|
||||
2409:8c85:200
|
||||
2409:8c85:2028
|
||||
@ -1654,7 +1159,6 @@
|
||||
2409:8c85:3c02
|
||||
2409:8c85:3c03
|
||||
2409:8c85:411
|
||||
2409:8c85:420
|
||||
2409:8c85:4400
|
||||
2409:8c85:4c00
|
||||
2409:8c85:5c00
|
||||
@ -1705,7 +1209,6 @@
|
||||
2409:8d38
|
||||
2409:8d4c
|
||||
2409:8d5a
|
||||
2409:8d5b
|
||||
2409:8d5c
|
||||
2409:8d5e
|
||||
2409:8d5f
|
||||
@ -1791,7 +1294,6 @@
|
||||
2409:8e34
|
||||
2409:8e38
|
||||
2409:8e4c
|
||||
2409:8e5a
|
||||
2409:8e5c
|
||||
2409:8e5e
|
||||
2409:8e5f
|
||||
|
@ -11,13 +11,9 @@
|
||||
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
|
||||
@ -28,6 +24,3 @@
|
||||
2408:8a06:1
|
||||
2408:8a06:100
|
||||
2408:8a06:101
|
||||
2408:8a26:ee10
|
||||
2408:8a26:f020
|
||||
2408:8a26:f950
|
||||
|
@ -194,15 +194,14 @@ func (t *Tracer) serveData(from net.IP, b []byte) error {
|
||||
Logger.Info(fmt.Sprintf("收到IPv6 ICMP消息: 类型=%v, 代码=%v", msg.Type, msg.Code))
|
||||
}
|
||||
// 处理不同类型的ICMP消息
|
||||
switch msg.Type {
|
||||
case ipv6.ICMPTypeEchoReply:
|
||||
if msg.Type == 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:
|
||||
} else if msg.Type == ipv6.ICMPTypeTimeExceeded {
|
||||
b = getReplyData(msg)
|
||||
if len(b) < ipv6.HeaderLen {
|
||||
if model.EnableLoger {
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/oneclickvirt/backtrace/model"
|
||||
. "github.com/oneclickvirt/defaultset"
|
||||
@ -63,66 +62,45 @@ func trace(ch chan Result, i int) {
|
||||
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 {
|
||||
// 先尝试原始IP地址
|
||||
hops, err := Trace(net.ParseIP(model.Ipv4s[i]))
|
||||
if err != nil {
|
||||
s := fmt.Sprintf("%v %-15s %v", model.Ipv4Names[i], model.Ipv4s[i], Red("检测不到回程路由节点的IP地址"))
|
||||
if model.EnableLoger {
|
||||
Logger.Error(fmt.Sprintf("追踪 %s (%s) 失败: %v", model.Ipv4Names[i], model.Ipv4s[i], err))
|
||||
}
|
||||
ch <- Result{i, s}
|
||||
}
|
||||
asns := extractIpv4ASNsFromHops(hops, model.EnableLoger)
|
||||
// 如果没有找到ASN,尝试备选IP
|
||||
if len(asns) == 0 {
|
||||
// 尝试从IcmpTargets获取备选IP
|
||||
if tryAltIPs := tryAlternativeIPs(model.Ipv4Names[i], "v4"); len(tryAltIPs) > 0 {
|
||||
for _, altIP := range tryAltIPs {
|
||||
if model.EnableLoger {
|
||||
Logger.Warn(fmt.Sprintf("第%d次追踪 %s (%s) 失败: %v", attemptNum, model.Ipv4Names[i], model.Ipv4s[i], err))
|
||||
Logger.Info(fmt.Sprintf("尝试备选IP %s 追踪 %s", altIP, model.Ipv4Names[i]))
|
||||
}
|
||||
// 如果原始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
|
||||
}
|
||||
hops, err = Trace(net.ParseIP(altIP))
|
||||
if err == nil && len(hops) > 0 {
|
||||
newAsns := extractIpv4ASNsFromHops(hops, model.EnableLoger)
|
||||
asns = append(asns, newAsns...)
|
||||
if len(newAsns) > 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)
|
||||
asns = removeDuplicates(asns)
|
||||
// // 记录每个hop的信息
|
||||
// if model.EnableLoger {
|
||||
// for hopNum, hop := range hops {
|
||||
// for nodeNum, node := range hop.Nodes {
|
||||
// Logger.Info(fmt.Sprintf("追踪 %s (%s) - Hop %d, Node %d: %s (RTT: %v)",
|
||||
// model.Ipv4Names[i], model.Ipv4s[i], hopNum+1, nodeNum+1, node.IP.String(), node.RTT))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// 处理不同线路
|
||||
if len(asns) > 0 {
|
||||
var tempText string
|
||||
@ -188,16 +166,18 @@ func trace(ch chan Result, i int) {
|
||||
}
|
||||
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))
|
||||
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]))
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/oneclickvirt/backtrace/model"
|
||||
. "github.com/oneclickvirt/defaultset"
|
||||
@ -12,7 +11,7 @@ import (
|
||||
"golang.org/x/net/ipv6"
|
||||
)
|
||||
|
||||
func newPacketV6(id uint16, _ net.IP, _ int) []byte {
|
||||
func newPacketV6(id uint16, dst net.IP, ttl int) []byte {
|
||||
// 使用ipv6包的Echo请求
|
||||
msg := icmp.Message{
|
||||
Type: ipv6.ICMPTypeEchoRequest,
|
||||
@ -78,66 +77,45 @@ func traceIPv6(ch chan Result, i int, offset int) {
|
||||
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 {
|
||||
// 先尝试原始IP地址
|
||||
hops, err := Trace(net.ParseIP(model.Ipv6s[i]))
|
||||
if err != nil {
|
||||
s := fmt.Sprintf("%v %-24s %v", model.Ipv6Names[i], model.Ipv6s[i], Red("检测不到回程路由节点的IP地址"))
|
||||
if model.EnableLoger {
|
||||
Logger.Warn(fmt.Sprintf("%s (%s) 检测不到回程路由节点的IP地址", model.Ipv6Names[i], model.Ipv6s[i]))
|
||||
}
|
||||
ch <- Result{i + offset, s}
|
||||
}
|
||||
asns := extractIpv6ASNsFromHops(hops, model.EnableLoger)
|
||||
// 如果没有找到ASN,尝试备选IP
|
||||
if len(asns) == 0 {
|
||||
// 尝试从IcmpTargets获取备选IP
|
||||
if tryAltIPs := tryAlternativeIPs(model.Ipv4Names[i], "v4"); len(tryAltIPs) > 0 {
|
||||
for _, altIP := range tryAltIPs {
|
||||
if model.EnableLoger {
|
||||
Logger.Warn(fmt.Sprintf("第%d次追踪 %s (%s) 失败: %v", attemptNum, model.Ipv6Names[i], model.Ipv6s[i], err))
|
||||
Logger.Info(fmt.Sprintf("尝试备选IP %s 追踪 %s", altIP, model.Ipv4Names[i]))
|
||||
}
|
||||
// 如果原始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
|
||||
}
|
||||
hops, err = Trace(net.ParseIP(altIP))
|
||||
if err == nil && len(hops) > 0 {
|
||||
newAsns := extractIpv6ASNsFromHops(hops, model.EnableLoger)
|
||||
asns = append(asns, newAsns...)
|
||||
if len(newAsns) > 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)
|
||||
asns = removeDuplicates(asns)
|
||||
// // 记录每个hop的信息
|
||||
// if model.EnableLoger {
|
||||
// for hopNum, hop := range hops {
|
||||
// for nodeNum, node := range hop.Nodes {
|
||||
// Logger.Info(fmt.Sprintf("追踪 %s (%s) - Hop %d, Node %d: %s (RTT: %v)",
|
||||
// model.Ipv6Names[i], model.Ipv6s[i], hopNum+1, nodeNum+1, node.IP.String(), node.RTT))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// 处理不同线路
|
||||
if len(asns) > 0 {
|
||||
var tempText string
|
||||
@ -208,7 +186,7 @@ func traceIPv6(ch chan Result, i int, offset int) {
|
||||
}
|
||||
}
|
||||
if model.EnableLoger {
|
||||
Logger.Info(fmt.Sprintf("%s (%s) 追踪完成,最终结果: %s", model.Ipv6Names[i], model.Ipv6s[i], tempText))
|
||||
Logger.Info(fmt.Sprintf("%s (%s) 追踪完成,结果: %s", model.Ipv6Names[i], model.Ipv6s[i], tempText))
|
||||
}
|
||||
ch <- Result{i + offset, tempText}
|
||||
} else {
|
||||
|
81
bk/utils.go
81
bk/utils.go
@ -70,11 +70,17 @@ func getData(endpoint string) string {
|
||||
|
||||
// parseIcmpTargets 解析ICMP目标数据
|
||||
func parseIcmpTargets(jsonData string) []model.IcmpTarget {
|
||||
// 确保JSON数据格式正确,如果返回的是数组,需要添加[和]
|
||||
if !strings.HasPrefix(jsonData, "[") {
|
||||
jsonData = "[" + jsonData + "]"
|
||||
}
|
||||
// 如果JSON数据中的对象没有正确用逗号分隔,修复它
|
||||
jsonData = strings.ReplaceAll(jsonData, "}{", "},{")
|
||||
var targets []model.IcmpTarget
|
||||
err := json.Unmarshal([]byte(jsonData), &targets)
|
||||
if err != nil {
|
||||
if model.EnableLoger {
|
||||
Logger.Info(fmt.Sprintf("解析ICMP目标失败: %s", err.Error()))
|
||||
Logger.Error(fmt.Sprintf("Failed to parse ICMP targets: %v", err))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -140,76 +146,3 @@ func tryAlternativeIPs(targetName string, ipVersion string) []string {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
87
cmd/main.go
87
cmd/main.go
@ -7,13 +7,9 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/oneclickvirt/backtrace/bgptools"
|
||||
backtrace "github.com/oneclickvirt/backtrace/bk"
|
||||
"github.com/oneclickvirt/backtrace/model"
|
||||
"github.com/oneclickvirt/backtrace/utils"
|
||||
. "github.com/oneclickvirt/defaultset"
|
||||
)
|
||||
|
||||
@ -25,27 +21,18 @@ type IpInfo struct {
|
||||
Org string `json:"org"`
|
||||
}
|
||||
|
||||
type ConcurrentResults struct {
|
||||
bgpResult string
|
||||
backtraceResult string
|
||||
bgpError error
|
||||
// backtraceError error
|
||||
}
|
||||
|
||||
func main() {
|
||||
go func() {
|
||||
http.Get("https://hits.spiritlhl.net/backtrace.svg?action=hit&title=Hits&title_bg=%23555555&count_bg=%230eecf8&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 specifiedIP string
|
||||
backtraceFlag := flag.NewFlagSet("backtrace", flag.ContinueOnError)
|
||||
backtraceFlag.BoolVar(&help, "h", false, "Show help information")
|
||||
backtraceFlag.BoolVar(&showVersion, "v", false, "Show version")
|
||||
backtraceFlag.BoolVar(&showIpInfo, "s", true, "Disabe show ip info")
|
||||
backtraceFlag.BoolVar(&model.EnableLoger, "log", 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:])
|
||||
if help {
|
||||
fmt.Printf("Usage: %s [options]\n", os.Args[0])
|
||||
@ -56,86 +43,24 @@ func main() {
|
||||
fmt.Println(model.BackTraceVersion)
|
||||
return
|
||||
}
|
||||
info := IpInfo{}
|
||||
if showIpInfo {
|
||||
rsp, err := http.Get("http://ipinfo.io")
|
||||
if err != nil {
|
||||
fmt.Printf("get ip info err %v \n", err.Error())
|
||||
fmt.Errorf("Get ip info err %v \n", err.Error())
|
||||
} else {
|
||||
info := IpInfo{}
|
||||
err = json.NewDecoder(rsp.Body).Decode(&info)
|
||||
if err != nil {
|
||||
fmt.Printf("json decode err %v \n", err.Error())
|
||||
fmt.Errorf("json decode err %v \n", err.Error())
|
||||
} else {
|
||||
fmt.Println(Green("国家: ") + White(info.Country) + Green(" 城市: ") + White(info.City) +
|
||||
Green(" 服务商: ") + Blue(info.Org))
|
||||
}
|
||||
}
|
||||
}
|
||||
preCheck := utils.CheckPublicAccess(3 * time.Second)
|
||||
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)
|
||||
}
|
||||
backtrace.BackTrace(ipv6)
|
||||
fmt.Println(Yellow("准确线路自行查看详细路由,本测试结果仅作参考"))
|
||||
fmt.Println(Yellow("同一目标地址多个线路时,检测可能已越过汇聚层,除第一个线路外,后续信息可能无效"))
|
||||
fmt.Println(Yellow("同一目标地址多个线路时,可能检测已越过汇聚层,除了第一个线路外,后续信息可能无效"))
|
||||
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
|
||||
fmt.Println("Press Enter to exit...")
|
||||
fmt.Scanln()
|
||||
|
36
go.mod
36
go.mod
@ -1,31 +1,33 @@
|
||||
module github.com/oneclickvirt/backtrace
|
||||
|
||||
go 1.24.5
|
||||
go 1.22.4
|
||||
|
||||
require (
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/imroc/req/v3 v3.54.0
|
||||
github.com/imroc/req/v3 v3.50.0
|
||||
github.com/oneclickvirt/defaultset v0.0.0-20240624051018-30a50859e1b5
|
||||
golang.org/x/net v0.41.0
|
||||
golang.org/x/sys v0.33.0
|
||||
golang.org/x/net v0.33.0
|
||||
golang.org/x/sys v0.28.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // indirect
|
||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||
github.com/cloudflare/circl v1.5.0 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // 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/klauspost/compress v1.17.11 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.22.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
|
||||
github.com/quic-go/quic-go v0.48.2 // indirect
|
||||
github.com/refraction-networking/utls v1.6.7 // indirect
|
||||
go.uber.org/mock v0.5.0 // indirect
|
||||
go.uber.org/multierr v1.10.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
|
||||
golang.org/x/crypto v0.31.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e // indirect
|
||||
golang.org/x/mod v0.22.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/tools v0.28.0 // indirect
|
||||
)
|
||||
|
82
go.sum
82
go.sum
@ -1,61 +1,71 @@
|
||||
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/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||
github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys=
|
||||
github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
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/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/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg=
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
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/imroc/req/v3 v3.50.0 h1:n3BVnZiTRpvkN5T1IB79LC/THhFU9iXksNRMH4ZNVaY=
|
||||
github.com/imroc/req/v3 v3.50.0/go.mod h1:tsOk8K7zI6cU4xu/VWCZVtq9Djw9IWm4MslKzme5woU=
|
||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||
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/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg=
|
||||
github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
|
||||
github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8=
|
||||
github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||
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/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE=
|
||||
github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
|
||||
github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM=
|
||||
github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0=
|
||||
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/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/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||
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.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
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=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e h1:4qufH0hlUYs6AO6XmZC3GqfDPGSXHVXUFR6OND+iJX4=
|
||||
golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
|
||||
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
||||
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
|
||||
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||
|
@ -2,7 +2,7 @@ package model
|
||||
|
||||
import "time"
|
||||
|
||||
const BackTraceVersion = "v0.0.7"
|
||||
const BackTraceVersion = "v0.0.5"
|
||||
|
||||
var EnableLoger = false
|
||||
|
||||
@ -15,7 +15,7 @@ type IcmpTarget struct {
|
||||
}
|
||||
|
||||
var (
|
||||
IcmpTargets = "https://raw.githubusercontent.com/spiritLHLS/icmp_targets/main/nodes.json"
|
||||
IcmpTargets = "https://raw.githubusercontent.com/spiritLHLS/icmp_targets/refs/heads/main/nodes.json"
|
||||
CdnList = []string{
|
||||
"http://cdn1.spiritlhl.net/",
|
||||
"http://cdn2.spiritlhl.net/",
|
||||
@ -76,82 +76,3 @@ var (
|
||||
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",
|
||||
}
|
||||
|
160
utils/utils.go
160
utils/utils.go
@ -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,
|
||||
}
|
||||
}
|
@ -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("❌ 本机未检测到公网连接")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user