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  }