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 = 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("』」") }