github.com/nxtrace/NTrace-core@v1.3.1-0.20240513132635-39169291e8c9/reporter/reporter.go (about) 1 package reporter 2 3 import ( 4 "fmt" 5 "net" 6 "strings" 7 "sync" 8 9 "github.com/nxtrace/NTrace-core/ipgeo" 10 "github.com/nxtrace/NTrace-core/trace" 11 ) 12 13 type Reporter interface { 14 Print() 15 } 16 17 func New(rs *trace.Result, ip string) Reporter { 18 experimentTag() 19 r := reporter{ 20 routeResult: rs, 21 targetIP: ip, 22 } 23 return &r 24 } 25 26 type reporter struct { 27 targetTTL uint16 28 targetIP string 29 routeReport map[uint16][]routeReportNode 30 routeReportLock sync.Mutex 31 routeResult *trace.Result 32 wg sync.WaitGroup 33 } 34 35 type routeReportNode struct { 36 asn string 37 isp string 38 geo []string 39 ix bool 40 } 41 42 func experimentTag() { 43 fmt.Println("Route-Path 功能实验室") 44 } 45 46 func (r *reporter) generateRouteReportNode(ip string, ipGeoData ipgeo.IPGeoData, ttl uint16) { 47 48 var success = true 49 50 defer r.wg.Done() 51 52 rpn := routeReportNode{} 53 ptr, err := net.LookupAddr(ip) 54 55 if err == nil { 56 if strings.Contains(strings.ToLower(ptr[0]), "ix") { 57 rpn.ix = true 58 } else { 59 rpn.ix = false 60 } 61 } 62 // TODO: 这种写法不好,后面再重构一下 63 // 判断反向解析的域名中又或者是IP地理位置数据库中,是否出现了 IX 64 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") { 65 rpn.ix = true 66 } 67 68 // TODO: 正则判断POP并且提取带宽大小等信息 69 70 // CN2 需要特殊处理,因为他们很多没有ASN 71 // 但是目前这种写法是不规范的,属于凭空标记4809的IP 72 // TODO: 用更好的方式显示 CN2 骨干网的路由 Path 73 if strings.HasPrefix(ip, "59.43") { 74 rpn.asn = "4809" 75 } else { 76 rpn.asn = ipGeoData.Asnumber 77 } 78 79 // 无论最后一跳是否为存在地理位置信息(AnyCast),都应该给予显示 80 if (ipGeoData.Country == "" || ipGeoData.Country == "LAN Address" || ipGeoData.Country == "-") && ip != r.targetIP { 81 success = false 82 } else { 83 if ipGeoData.City == "" { 84 rpn.geo = []string{ipGeoData.Country, ipGeoData.Prov} 85 } else { 86 rpn.geo = []string{ipGeoData.Country, ipGeoData.City} 87 } 88 } 89 if ipGeoData.Asnumber == "" { 90 rpn.asn = "*" 91 } 92 93 if ipGeoData.Isp == "" { 94 rpn.isp = ipGeoData.Owner 95 } else { 96 rpn.isp = ipGeoData.Isp 97 } 98 99 // 有效记录 100 if success { 101 // 锁住资源,防止同时写panic 102 r.routeReportLock.Lock() 103 // 添加到MAP中 104 r.routeReport[ttl] = append(r.routeReport[ttl], rpn) 105 // 写入完成,解锁释放资源给其他协程 106 r.routeReportLock.Unlock() 107 } 108 } 109 110 func (r *reporter) InitialBaseData() Reporter { 111 reportNodes := map[uint16][]routeReportNode{} 112 113 r.routeReport = reportNodes 114 r.targetTTL = uint16(len(r.routeResult.Hops)) 115 116 for i := uint16(0); i < r.targetTTL; i++ { 117 if i < uint16(len(r.routeResult.Hops)) && len(r.routeResult.Hops[i]) > 0 { 118 traceHop := r.routeResult.Hops[i][0] 119 if traceHop.Success { 120 currentIP := traceHop.Address.String() 121 r.wg.Add(1) 122 go r.generateRouteReportNode(currentIP, *traceHop.Geo, i) 123 } 124 } 125 } 126 127 // 等待所有的子协程运行完毕 128 r.wg.Wait() 129 return r 130 } 131 132 func (r *reporter) Print() { 133 var beforeActiveTTL uint16 = 0 134 r.InitialBaseData() 135 // 尝试首个有效 TTL 136 for i := uint16(0); i < r.targetTTL; i++ { 137 if len(r.routeReport[i]) != 0 { 138 beforeActiveTTL = i 139 // 找到以后便不再循环 140 break 141 } 142 } 143 144 for i := beforeActiveTTL; i < r.targetTTL; i++ { 145 // 计算该TTL内的数据长度,如果为0,则代表没有有效数据 146 if len(r.routeReport[i]) == 0 { 147 // 跳过改跃点的数据整理 148 continue 149 } 150 nodeReport := r.routeReport[i][0] 151 152 if i == beforeActiveTTL { 153 fmt.Printf("AS%s %s「%s『%s", nodeReport.asn, nodeReport.isp, nodeReport.geo[0], nodeReport.geo[1]) 154 } else { 155 nodeReportBefore := r.routeReport[beforeActiveTTL][0] 156 // ASN 相同,同个 ISP 内部的数据传递 157 if nodeReportBefore.asn == nodeReport.asn { 158 // Same ASN but Coutry or City Changed 159 if nodeReportBefore.geo[0] != nodeReport.geo[0] { 160 fmt.Printf("』→ %s『%s", nodeReport.geo[0], nodeReport.geo[1]) 161 } else { 162 if nodeReportBefore.geo[1] != nodeReport.geo[1] { 163 fmt.Printf(" → %s", nodeReport.geo[1]) 164 } 165 } 166 } else { 167 // ASN 不同,跨 ISP 的数据传递,这里可能会出现 POP、IP Transit、Peer、Exchange 168 fmt.Printf("』」") 169 if int(i) != len(r.routeReport)+1 { 170 // 部分 Shell 客户端可能无法很好的展示这个特殊字符 171 // TODO: 寻找其他替代字符 172 fmt.Printf("\n ╭╯\n ╰") 173 } 174 if nodeReport.ix { 175 fmt.Printf("AS%s \033[42;37mIXP\033[0m %s「%s『%s", nodeReport.asn, nodeReport.isp, nodeReport.geo[0], nodeReport.geo[1]) 176 } else { 177 fmt.Printf("AS%s %s「%s『%s", nodeReport.asn, nodeReport.isp, nodeReport.geo[0], nodeReport.geo[1]) 178 } 179 } 180 } 181 // 标记为最新的一个有效跃点 182 beforeActiveTTL = i 183 } 184 fmt.Println("』」") 185 }