diff --git a/README.md b/README.md index d6a1b11..1f2b559 100644 --- a/README.md +++ b/README.md @@ -15,13 +15,10 @@ - [x] 支持对```CTGNET```、```CN2GIA```和```CN2GT```线路的判断 - [x] 支持对```CMIN2```和```CMI```线路的判断 - [x] 支持对整个回程路由进行线路分析,一个目标IP可能会分析出多种线路 +- [x] 支持对主流接入点的线路检测,方便分析国际互联能力 - [x] 增加对全平台的编译支持,原版[backtrace](https://github.com/zhanghanyun/backtrace)仅支持linux平台的amd64和arm64架构 - [x] 兼容额外的ICMP地址获取,若当前目标IP无法查询路由尝试额外的IP地址 -## TODO - -- [ ] 添加对主流ISP的POP点检测,区分国际互联能力 - ## 使用 下载、安装、更新 diff --git a/bgptools/pop.go b/bgptools/pop.go index f3feb40..0f0bbc9 100644 --- a/bgptools/pop.go +++ b/bgptools/pop.go @@ -10,6 +10,7 @@ import ( "github.com/google/uuid" "github.com/imroc/req/v3" + "github.com/oneclickvirt/backtrace/model" ) type ASCard struct { @@ -39,82 +40,32 @@ type PoPResult struct { Result string } -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", -} - -var tier1Regional = map[string]string{ - "4134": "ChinaNet", - "4837": "China Unicom", - "9808": "China Mobile", - "4766": "Korea Telecom", - "2516": "KDDI", - "7713": "Telkomnet", - "9121": "Etisalat", -} - -var tier2 = map[string]string{ - "6939": "Hurricane Electric", - "20485": "Transtelecom", - "1273": "Vodafone", - "1239": "Sprint", - "6762": "Sparkle", - "6453": "Tata", -} - -var contentProviders = map[string]string{ - "15169": "Google", - "32934": "Facebook", - "54113": "Fastly", - "20940": "Akamai", - "13335": "Cloudflare", -} - -var ixps = map[string]string{ - "5539": "IX.br", - "25291": "HKIX", - "1200": "AMS-IX", - "6695": "DE-CIX", -} - func getISPAbbr(asn, name string) string { - if abbr, ok := tier1Global[asn]; ok { + if abbr, ok := model.Tier1Global[asn]; ok { return abbr } - if idx := strings.Index(name, " "); idx != -1 { + if idx := strings.Index(name, " "); idx != -1 && idx > 18 { return name[:idx] } return name } -func getISPType(asn string, tier1 bool) string { +func getISPType(asn string, tier1 bool, direct bool) string { switch { - case tier1 && tier1Global[asn] != "": + case tier1 && model.Tier1Global[asn] != "": return "Tier1 Global" - case tier1Regional[asn] != "": + case model.Tier1Regional[asn] != "": return "Tier1 Regional" - case tier2[asn] != "": + case model.Tier2[asn] != "": return "Tier2" - case contentProviders[asn] != "": + case model.ContentProviders[asn] != "": return "CDN Provider" - case ixps[asn] != "": + case model.IXPS[asn] != "": return "IXP" - default: + case direct: return "Direct" + default: + return "Indirect" } } @@ -236,7 +187,7 @@ func findUpstreams(targetASN string, nodes []ASCard, edges []Arrow) []Upstream { continue } isTier1 := (n.Fill == "white" && n.Stroke == "#005ea5") - upstreamType := getISPType(n.ASN, isTier1) + upstreamType := getISPType(n.ASN, isTier1, true) upstreams = append(upstreams, Upstream{ ASN: n.ASN, Name: n.Name, @@ -245,6 +196,55 @@ func findUpstreams(targetASN string, nodes []ASCard, edges []Arrow) []Upstream { Type: upstreamType, }) } + 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 + } + found := false + for _, existing := range upstreams { + if existing.ASN == nextASN { + found = true + break + } + } + if found { + 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, + }) + currentASN = nextASN + } + } return upstreams } @@ -270,33 +270,39 @@ func GetPoPInfo(ip string) (*PoPResult, error) { return nil, fmt.Errorf("无法识别目标 ASN") } upstreams := findUpstreams(targetASN, nodes, edges) - if len(upstreams) > 5 { - upstreams = upstreams[:5] - } - colWidth := 16 + colWidth := 18 center := func(s string) string { runeLen := len([]rune(s)) if runeLen >= colWidth { - return s[:colWidth] + return string([]rune(s)[:colWidth]) } padding := colWidth - runeLen left := padding / 2 right := padding - left return strings.Repeat(" ", left) + s + strings.Repeat(" ", right) } - var line1, line2, line3 []string - for _, u := range upstreams { - abbr := getISPAbbr(u.ASN, u.Name) - line1 = append(line1, center("AS"+u.ASN)) - line2 = append(line2, center(abbr)) - line3 = append(line3, center(u.Type)) - } var result strings.Builder - result.WriteString(strings.Join(line1, "")) - result.WriteString("\n") - result.WriteString(strings.Join(line2, "")) - result.WriteString("\n") - result.WriteString(strings.Join(line3, "")) + 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) + line1 = append(line1, center("AS"+u.ASN)) + line2 = append(line2, center(abbr)) + line3 = append(line3, center(u.Type)) + } + 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, diff --git a/bgptools/pop_test.go b/bgptools/pop_test.go index b273daa..05be502 100644 --- a/bgptools/pop_test.go +++ b/bgptools/pop_test.go @@ -6,12 +6,14 @@ import ( ) func TestGetPoPInfo(t *testing.T) { - result, err := GetPoPInfo("66.70.153.71") + result, err := GetPoPInfo("206.190.233.1") 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) } diff --git a/cmd/main.go b/cmd/main.go index fa15297..f742b60 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -9,6 +9,7 @@ import ( "runtime" "time" + "github.com/oneclickvirt/backtrace/bgptools" backtrace "github.com/oneclickvirt/backtrace/bk" "github.com/oneclickvirt/backtrace/model" "github.com/oneclickvirt/backtrace/utils" @@ -45,12 +46,12 @@ func main() { fmt.Println(model.BackTraceVersion) return } + info := IpInfo{} if showIpInfo { rsp, err := http.Get("http://ipinfo.io") if err != nil { fmt.Errorf("Get ip info err %v \n", err.Error()) } else { - info := IpInfo{} err = json.NewDecoder(rsp.Body).Decode(&info) if err != nil { fmt.Errorf("json decode err %v \n", err.Error()) @@ -61,6 +62,15 @@ func main() { } } preCheck := utils.CheckPublicAccess(3 * time.Second) + if preCheck.Connected && info.Ip != "" { + result, err := bgptools.GetPoPInfo(info.Ip) + if err != nil { + fmt.Println(err.Error()) + return + } + fmt.Println("上游信息:") + fmt.Print(result.Result) + } if preCheck.Connected && preCheck.StackType == "DualStack" { backtrace.BackTrace(ipv6) } else if preCheck.Connected && preCheck.StackType == "IPv4" { diff --git a/model/model.go b/model/model.go index ec50a1c..9a0c30c 100644 --- a/model/model.go +++ b/model/model.go @@ -76,3 +76,82 @@ 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", +} \ No newline at end of file