diff --git a/main.go b/main.go index c624d48..647cae6 100644 --- a/main.go +++ b/main.go @@ -31,6 +31,7 @@ var dataOrigin = fSet.String("d", "LeoMoeAPI", "Choose IP Geograph Data Provider var noRdns = fSet.Bool("n", false, "Disable IP Reverse DNS lookup") var routePath = fSet.Bool("report", false, "Route Path") var tablePrint = fSet.Bool("table", false, "Output trace results as table") +var classicPrint = fSet.Bool("classic", false, "Classic Output trace results like BestTrace") var beginHop = fSet.Int("b", 1, "Set The Begin TTL") var ver = fSet.Bool("V", false, "Print Version") @@ -59,6 +60,7 @@ func flagApply() string { // Print Version if *ver { + printer.CopyRight() os.Exit(0) } @@ -129,7 +131,11 @@ func main() { } if !*tablePrint { - conf.RealtimePrinter = printer.RealtimePrinter + if *classicPrint { + conf.RealtimePrinter = printer.ClassicPrinter + } else { + conf.RealtimePrinter = printer.RealtimePrinter + } } res, err := trace.Traceroute(m, conf) diff --git a/printer/basic.go b/printer/basic.go index 439411b..53a3131 100644 --- a/printer/basic.go +++ b/printer/basic.go @@ -3,6 +3,8 @@ package printer import ( "fmt" "net" + + "github.com/fatih/color" ) var version = "v0.0.0.alpha" @@ -10,8 +12,16 @@ var buildDate = "" var commitID = "" func Version() { - fmt.Println("NextTrace", version, buildDate, commitID) - fmt.Println("XGadget-lab Leo (leo.moe) & Vincent (vincent.moe) & zhshch (xzhsh.ch)") + fmt.Fprintf(color.Output, "%s %s %s %s\n", + color.New(color.FgWhite, color.Bold).Sprintf("%s", "NextTrace"), + color.New(color.FgHiBlack, color.Bold).Sprintf("%s", version), + color.New(color.FgHiBlack, color.Bold).Sprintf("%s", buildDate), + color.New(color.FgHiBlack, color.Bold).Sprintf("%s", commitID), + ) +} + +func CopyRight() { + fmt.Println("XGadget-lab Leo (leo.moe) & Tso (tsosunchia@gmail.com) & Vincent (vincent.moe) & zhshch (xzhsh.ch)") } func PrintTraceRouteNav(ip net.IP, domain string, dataOrigin string) { diff --git a/printer/classic_printer.go b/printer/classic_printer.go new file mode 100644 index 0000000..1f206a9 --- /dev/null +++ b/printer/classic_printer.go @@ -0,0 +1,104 @@ +package printer + +import ( + "fmt" + "strings" + + "github.com/xgadget-lab/nexttrace/trace" +) + +type HopInfo int + +const ( + General HopInfo = 0 + IXP HopInfo = 1 + Peer HopInfo = 2 + PoP HopInfo = 3 + Aboard HopInfo = 4 +) + +func findLatestAvailableHop(res *trace.Result, ttl int, probesIndex int) int { + for ttl > 0 { + // 查找上一个跃点是不是有效结果 + ttl-- + // 判断此TTL跃点是否有效并判断地理位置结构体是否已经初始化 + if len(res.Hops[ttl]) != 0 && res.Hops[ttl][probesIndex].Success && res.Hops[ttl][probesIndex].Geo != nil { + // TTL虽有效,但地理位置API没有能够正确返回数据,依旧不能视为有效数据 + if res.Hops[ttl][probesIndex].Geo.Country == "" { + // 跳过继续寻找上一个有效跃点 + continue + } + return ttl + } + } + // 没找到 + return -1 +} + +func unifyName(name string) string { + if name == "China" || name == "CN" { + return "中国" + } else if name == "Hong kong" || name == "香港" || name == "Central and Western" { + return "中国香港" + } else if name == "Taiwan" || name == "台湾" { + return "中国台湾" + } else { + return name + } +} + +func chinaISPPeer(hostname string) bool { + var keyWords = []string{"china", "ct", "cu", "cm", "cnc", "4134", "4837", "4809", "9929"} + for _, k := range keyWords { + if strings.Contains(strings.ToLower(hostname), k) { + return true + } + } + return false +} + +func chinaMainland(h trace.Hop) bool { + if unifyName(h.Geo.Country) == "中国" && unifyName(h.Geo.Prov) != "中国香港" && unifyName(h.Geo.Prov) != "中国台湾" { + return true + } else { + return false + } +} + +func makeHopsType(res *trace.Result, ttl int) map[int]HopInfo { + // 创建一个字典,存放所有当前TTL的跃点类型集合 + hopProbesMap := make(map[int]HopInfo) + for i := range res.Hops[ttl] { + // 判断是否res.Hops[ttl][i]是一个有效的跃点并且地理位置信息已经初始化 + if res.Hops[ttl][i].Success && res.Hops[ttl][i].Geo != nil { + if availableTTL := findLatestAvailableHop(res, ttl, i); availableTTL != -1 { + switch { + case strings.Contains(res.Hops[ttl][i].Geo.District, "IXP") || strings.Contains(strings.ToLower(res.Hops[ttl][i].Hostname), "ix"): + hopProbesMap[i] = IXP + case strings.Contains(res.Hops[ttl][i].Geo.District, "Peer") || chinaISPPeer(res.Hops[ttl][i].Hostname): + hopProbesMap[i] = Peer + case strings.Contains(res.Hops[ttl][i].Geo.District, "PoP"): + hopProbesMap[i] = PoP + // 2个有效跃点必须都为有效数据,如果当前跳没有地理位置信息或者为局域网,不能视为有效节点 + case res.Hops[availableTTL][i].Geo.Country != "LAN Address" && res.Hops[ttl][i].Geo.Country != "LAN Address" && res.Hops[ttl][i].Geo.Country != "" && + // 一个跃点在中国大陆,另外一个跃点在其他地区,则可以推断出数据包跨境 + chinaMainland(res.Hops[availableTTL][i]) != chinaMainland(res.Hops[ttl][i]): + // TODO: 将先后2跳跃点信息汇报给API,以完善相关数据 + hopProbesMap[i] = Aboard + } + } else { + hopProbesMap[i] = General + } + } + } + + return hopProbesMap +} + +func ClassicPrinter(res *trace.Result, ttl int) { + fmt.Print(ttl + 1) + hopsTypeMap := makeHopsType(res, ttl) + for i := range res.Hops[ttl] { + HopPrinter(res.Hops[ttl][i], hopsTypeMap[i]) + } +} diff --git a/printer/realtime_printer.go b/printer/realtime_printer.go index ec1bbc6..97887c7 100644 --- a/printer/realtime_printer.go +++ b/printer/realtime_printer.go @@ -2,103 +2,89 @@ package printer import ( "fmt" - "strings" + "strconv" + "github.com/fatih/color" "github.com/xgadget-lab/nexttrace/trace" ) -type HopInfo int - -const ( - General HopInfo = 0 - IXP HopInfo = 1 - Peer HopInfo = 2 - PoP HopInfo = 3 - Aboard HopInfo = 4 -) - -func findLatestAvailableHop(res *trace.Result, ttl int, probesIndex int) int { - for ttl > 0 { - // 查找上一个跃点是不是有效结果 - ttl-- - // 判断此TTL跃点是否有效并判断地理位置结构体是否已经初始化 - if len(res.Hops[ttl]) != 0 && res.Hops[ttl][probesIndex].Success && res.Hops[ttl][probesIndex].Geo != nil { - // TTL虽有效,但地理位置API没有能够正确返回数据,依旧不能视为有效数据 - if res.Hops[ttl][probesIndex].Geo.Country == "" { - // 跳过继续寻找上一个有效跃点 - continue - } - return ttl - } - } - // 没找到 - return -1 -} - -func unifyName(name string) string { - if name == "China" || name == "CN" { - return "中国" - } else if name == "Hong kong" || name == "香港" || name == "Central and Western" { - return "中国香港" - } else if name == "Taiwan" || name == "台湾" { - return "中国台湾" - } else { - return name - } -} - -func chinaISPPeer(hostname string) bool { - var keyWords = []string{"china", "ct", "cu", "cm", "cnc", "4134", "4837", "4809", "9929"} - for _, k := range keyWords { - if strings.Contains(strings.ToLower(hostname), k) { - return true - } - } - return false -} - -func chinaMainland(h trace.Hop) bool { - if unifyName(h.Geo.Country) == "中国" && unifyName(h.Geo.Prov) != "中国香港" && unifyName(h.Geo.Prov) != "中国台湾" { - return true - } else { - return false - } -} - -func makeHopsType(res *trace.Result, ttl int) map[int]HopInfo { - // 创建一个字典,存放所有当前TTL的跃点类型集合 - hopProbesMap := make(map[int]HopInfo) - for i := range res.Hops[ttl] { - // 判断是否res.Hops[ttl][i]是一个有效的跃点并且地理位置信息已经初始化 - if res.Hops[ttl][i].Success && res.Hops[ttl][i].Geo != nil { - if availableTTL := findLatestAvailableHop(res, ttl, i); availableTTL != -1 { - switch { - case strings.Contains(res.Hops[ttl][i].Geo.District, "IXP") || strings.Contains(strings.ToLower(res.Hops[ttl][i].Hostname), "ix"): - hopProbesMap[i] = IXP - case strings.Contains(res.Hops[ttl][i].Geo.District, "Peer") || chinaISPPeer(res.Hops[ttl][i].Hostname): - hopProbesMap[i] = Peer - case strings.Contains(res.Hops[ttl][i].Geo.District, "PoP"): - hopProbesMap[i] = PoP - // 2个有效跃点必须都为有效数据,如果当前跳没有地理位置信息或者为局域网,不能视为有效节点 - case res.Hops[availableTTL][i].Geo.Country != "LAN Address" && res.Hops[ttl][i].Geo.Country != "LAN Address" && res.Hops[ttl][i].Geo.Country != "" && - // 一个跃点在中国大陆,另外一个跃点在其他地区,则可以推断出数据包跨境 - chinaMainland(res.Hops[availableTTL][i]) != chinaMainland(res.Hops[ttl][i]): - // TODO: 将先后2跳跃点信息汇报给API,以完善相关数据 - hopProbesMap[i] = Aboard - } - } else { - hopProbesMap[i] = General - } - } - } - - return hopProbesMap -} - func RealtimePrinter(res *trace.Result, ttl int) { - fmt.Print(ttl + 1) - hopsTypeMap := makeHopsType(res, ttl) - for i := range res.Hops[ttl] { - HopPrinter(res.Hops[ttl][i], hopsTypeMap[i]) + fmt.Printf("%s ", color.New(color.FgHiYellow, color.Bold).Sprintf("%-2d", ttl+1)) + + // 去重 + var latestIP string + tmpMap := make(map[string][]string) + for i, v := range res.Hops[ttl] { + if v.Address == nil && latestIP != "" { + tmpMap[latestIP] = append(tmpMap[latestIP], fmt.Sprintf("%s ms", "*")) + continue + } else if v.Address == nil { + continue + } + + if _, exist := tmpMap[v.Address.String()]; !exist { + tmpMap[v.Address.String()] = append(tmpMap[v.Address.String()], strconv.Itoa(i)) + // 首次进入 + if latestIP == "" { + for j := 0; j < i; j++ { + tmpMap[v.Address.String()] = append(tmpMap[v.Address.String()], fmt.Sprintf("%s ms", "*")) + } + } + latestIP = v.Address.String() + } + + tmpMap[v.Address.String()] = append(tmpMap[v.Address.String()], fmt.Sprintf("%.2f ms", v.RTT.Seconds()*1000)) + } + + if latestIP == "" { + fmt.Fprintf(color.Output, "%s\n", + color.New(color.FgWhite, color.Bold).Sprintf("*"), + ) + return + } + + var blockDisplay = false + for ip, v := range tmpMap { + if blockDisplay { + fmt.Printf("%4s", "") + } + fmt.Fprintf(color.Output, "%s", + color.New(color.FgWhite, color.Bold).Sprintf("%-15s", ip), + ) + + i, _ := strconv.Atoi(v[0]) + + if res.Hops[ttl][i].Geo.Asnumber != "" { + fmt.Fprintf(color.Output, " %s", color.New(color.FgHiGreen, color.Bold).Sprintf("AS%-6s", res.Hops[ttl][i].Geo.Asnumber)) + } else { + fmt.Printf(" %-8s", "*") + } + + if res.Hops[ttl][i].Geo.Country == "" { + res.Hops[ttl][i].Geo.Country = "LAN Address" + } + + fmt.Fprintf(color.Output, " %s %s %s %s %s\n %s ", + color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.Country), + color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.Prov), + color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.City), + color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.District), + fmt.Sprintf("%-6s", res.Hops[ttl][i].Geo.Owner), + color.New(color.FgHiBlack, color.Bold).Sprintf("%-22s", res.Hops[ttl][0].Hostname), + ) + + for j := 1; j < len(v); j++ { + if len(v) == 2 || j == 1 { + fmt.Fprintf(color.Output, "%s", + color.New(color.FgHiCyan, color.Bold).Sprintf("%s", v[j]), + ) + } else { + fmt.Fprintf(color.Output, " / %s", + color.New(color.FgHiCyan, color.Bold).Sprintf("%s", v[j]), + ) + } + } + fmt.Println() + blockDisplay = true } } diff --git a/util/util.go b/util/util.go index b981b38..7ec169a 100644 --- a/util/util.go +++ b/util/util.go @@ -5,6 +5,8 @@ import ( "log" "net" "os" + + "github.com/fatih/color" ) // get the local ip and port based on our destination ip @@ -60,7 +62,10 @@ func DomainLookUp(host string, ipv4Only bool) net.IP { } else { fmt.Println("Please Choose the IP You Want To TraceRoute") for i, ip := range ipSlice { - fmt.Printf("%d. %s\n", i, ip) + fmt.Fprintf(color.Output, "%s %s\n", + color.New(color.FgHiYellow, color.Bold).Sprintf("%d.", i), + color.New(color.FgWhite, color.Bold).Sprintf("%s", ip), + ) } var index int fmt.Printf("Your Option: ")