mirror of
https://github.com/nxtrace/NTrace-core.git
synced 2025-08-12 06:26:39 +00:00
184 lines
4.8 KiB
Go
184 lines
4.8 KiB
Go
package reporter
|
||
|
||
import (
|
||
"fmt"
|
||
"net"
|
||
"strings"
|
||
"sync"
|
||
|
||
"github.com/xgadget-lab/nexttrace/ipgeo"
|
||
"github.com/xgadget-lab/nexttrace/trace"
|
||
)
|
||
|
||
type Reporter interface {
|
||
Print()
|
||
}
|
||
|
||
func New(rs *trace.Result, ip string) Reporter {
|
||
experimentTag()
|
||
r := reporter{
|
||
routeResult: rs,
|
||
targetIP: ip,
|
||
}
|
||
return &r
|
||
}
|
||
|
||
type reporter struct {
|
||
targetTTL uint16
|
||
targetIP string
|
||
routeReport map[uint16][]routeReportNode
|
||
routeReportLock sync.Mutex
|
||
routeResult *trace.Result
|
||
wg sync.WaitGroup
|
||
}
|
||
|
||
type routeReportNode struct {
|
||
asn string
|
||
isp string
|
||
geo []string
|
||
ix bool
|
||
}
|
||
|
||
func experimentTag() {
|
||
fmt.Println("Route-Path 功能实验室")
|
||
}
|
||
|
||
func (r *reporter) generateRouteReportNode(ip string, ipGeoData ipgeo.IPGeoData, ttl uint16) {
|
||
|
||
var success bool = true
|
||
|
||
defer r.wg.Done()
|
||
|
||
rpn := routeReportNode{}
|
||
ptr, err := net.LookupAddr(ip)
|
||
|
||
if err == nil {
|
||
if strings.Contains(strings.ToLower(ptr[0]), "ix") {
|
||
rpn.ix = true
|
||
} else {
|
||
rpn.ix = false
|
||
}
|
||
}
|
||
// TODO: 这种写法不好,后面再重构一下
|
||
// 判断反向解析的域名中又或者是IP地理位置数据库中,是否出现了 IX
|
||
if strings.Contains(strings.ToLower(ipGeoData.Isp), "exchange") || strings.Contains(strings.ToLower(ipGeoData.Isp), "ix") || strings.Contains(strings.ToLower(ipGeoData.Owner), "exchange") || strings.Contains(strings.ToLower(ipGeoData.Owner), "ix") {
|
||
rpn.ix = true
|
||
}
|
||
|
||
// TODO: 正则判断POP并且提取带宽大小等信息
|
||
|
||
// CN2 需要特殊处理,因为他们很多没有ASN
|
||
// 但是目前这种写法是不规范的,属于凭空标记4809的IP
|
||
// TODO: 用更好的方式显示 CN2 骨干网的路由 Path
|
||
if strings.HasPrefix(ip, "59.43") {
|
||
rpn.asn = "4809"
|
||
} else {
|
||
rpn.asn = ipGeoData.Asnumber
|
||
}
|
||
|
||
// 无论最后一跳是否为存在地理位置信息(AnyCast),都应该给予显示
|
||
if (ipGeoData.Country == "" || ipGeoData.Country == "LAN Address" || ipGeoData.Country == "-") && ip != r.targetIP {
|
||
success = false
|
||
} else {
|
||
if ipGeoData.City == "" {
|
||
rpn.geo = []string{ipGeoData.Country, ipGeoData.Prov}
|
||
} else {
|
||
rpn.geo = []string{ipGeoData.Country, ipGeoData.City}
|
||
}
|
||
}
|
||
if ipGeoData.Asnumber == "" {
|
||
rpn.asn = "*"
|
||
}
|
||
|
||
if ipGeoData.Isp == "" {
|
||
rpn.isp = ipGeoData.Owner
|
||
} else {
|
||
rpn.isp = ipGeoData.Isp
|
||
}
|
||
|
||
// 有效记录
|
||
if success {
|
||
// 锁住资源,防止同时写panic
|
||
r.routeReportLock.Lock()
|
||
// 添加到MAP中
|
||
r.routeReport[ttl] = append(r.routeReport[ttl], rpn)
|
||
// 写入完成,解锁释放资源给其他协程
|
||
r.routeReportLock.Unlock()
|
||
}
|
||
}
|
||
|
||
func (r *reporter) InitialBaseData() Reporter {
|
||
reportNodes := map[uint16][]routeReportNode{}
|
||
|
||
r.routeReport = reportNodes
|
||
r.targetTTL = uint16(len(r.routeResult.Hops))
|
||
|
||
for i := uint16(0); i < r.targetTTL; i++ {
|
||
traceHop := r.routeResult.Hops[i][0]
|
||
if traceHop.Success {
|
||
currentIP := traceHop.Address.String()
|
||
r.wg.Add(1)
|
||
go r.generateRouteReportNode(currentIP, *traceHop.Geo, i)
|
||
}
|
||
}
|
||
|
||
// 等待所有的子协程运行完毕
|
||
r.wg.Wait()
|
||
return r
|
||
}
|
||
|
||
func (r *reporter) Print() {
|
||
var beforeActiveTTL uint16 = 0
|
||
r.InitialBaseData()
|
||
// 尝试首个有效 TTL
|
||
for i := uint16(0); i < r.targetTTL; i++ {
|
||
if len(r.routeReport[i]) != 0 {
|
||
beforeActiveTTL = i
|
||
// 找到以后便不再循环
|
||
break
|
||
}
|
||
}
|
||
|
||
for i := beforeActiveTTL; i < r.targetTTL; i++ {
|
||
// 计算该TTL内的数据长度,如果为0,则代表没有有效数据
|
||
if len(r.routeReport[i]) == 0 {
|
||
// 跳过改跃点的数据整理
|
||
continue
|
||
}
|
||
nodeReport := r.routeReport[i][0]
|
||
|
||
if i == beforeActiveTTL {
|
||
fmt.Printf("AS%s %s「%s『%s", nodeReport.asn, nodeReport.isp, nodeReport.geo[0], nodeReport.geo[1])
|
||
} else {
|
||
nodeReportBefore := r.routeReport[beforeActiveTTL][0]
|
||
// ASN 相同,同个 ISP 内部的数据传递
|
||
if nodeReportBefore.asn == nodeReport.asn {
|
||
// Same ASN but Coutry or City Changed
|
||
if nodeReportBefore.geo[0] != nodeReport.geo[0] {
|
||
fmt.Printf("』→ %s『%s", nodeReport.geo[0], nodeReport.geo[1])
|
||
} else {
|
||
if nodeReportBefore.geo[1] != nodeReport.geo[1] {
|
||
fmt.Printf(" → %s", nodeReport.geo[1])
|
||
}
|
||
}
|
||
} else {
|
||
// ASN 不同,跨 ISP 的数据传递,这里可能会出现 POP、IP Transit、Peer、Exchange
|
||
fmt.Printf("』」")
|
||
if int(i) != len(r.routeReport)+1 {
|
||
// 部分 Shell 客户端可能无法很好的展示这个特殊字符
|
||
// TODO: 寻找其他替代字符
|
||
fmt.Printf("\n ╭╯\n ╰")
|
||
}
|
||
if nodeReport.ix {
|
||
fmt.Printf("AS%s \033[42;37mIXP\033[0m %s「%s『%s", nodeReport.asn, nodeReport.isp, nodeReport.geo[0], nodeReport.geo[1])
|
||
} else {
|
||
fmt.Printf("AS%s %s「%s『%s", nodeReport.asn, nodeReport.isp, nodeReport.geo[0], nodeReport.geo[1])
|
||
}
|
||
}
|
||
}
|
||
// 标记为最新的一个有效跃点
|
||
beforeActiveTTL = i
|
||
}
|
||
fmt.Println("』」")
|
||
}
|