mirror of
				https://github.com/oneclickvirt/backtrace.git
				synced 2025-11-04 07:42:37 +08:00 
			
		
		
		
	init
This commit is contained in:
		
							parent
							
								
									11df342bc1
								
							
						
					
					
						commit
						8b15256f69
					
				@ -1,2 +1,5 @@
 | 
			
		||||
# backtrace
 | 
			
		||||
 | 
			
		||||
[](https://hits.seeyoufarm.com)
 | 
			
		||||
 | 
			
		||||
基于 https://github.com/zhanghanyun/backtrace 的重构和优化版本
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										143
									
								
								backtrace/asn.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								backtrace/asn.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,143 @@
 | 
			
		||||
package backtrace
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	. "github.com/oneclickvirt/backtrace/defaultset"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Result struct {
 | 
			
		||||
	i int
 | 
			
		||||
	s string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	ips = []string{
 | 
			
		||||
		"219.141.136.12", "202.106.50.1", "221.179.155.161",
 | 
			
		||||
		"202.96.209.133", "210.22.97.1", "211.136.112.200",
 | 
			
		||||
		"58.60.188.222", "210.21.196.6", "120.196.165.24",
 | 
			
		||||
		"61.139.2.69", "119.6.6.6", "211.137.96.205",
 | 
			
		||||
	}
 | 
			
		||||
	names = []string{
 | 
			
		||||
		"北京电信", "北京联通", "北京移动",
 | 
			
		||||
		"上海电信", "上海联通", "上海移动",
 | 
			
		||||
		"广州电信", "广州联通", "广州移动",
 | 
			
		||||
		"成都电信", "成都联通", "成都移动",
 | 
			
		||||
	}
 | 
			
		||||
	m = map[string]string{
 | 
			
		||||
		// [] 前的字符串个数,中文占2个字符串
 | 
			
		||||
		"AS4809a": " 电信CN2GIA [优质线路]", // 18
 | 
			
		||||
		"AS4809b": " 电信CN2GT  [精品线路]", // 18
 | 
			
		||||
		"AS4134":  " 电信163    [普通线路]", // 18
 | 
			
		||||
		"AS9929":  " 联通9929   [优质线路]", // 18
 | 
			
		||||
		"AS4837":  " 联通4837   [普通线路]", // 18
 | 
			
		||||
		"AS58807": "移动CMIN2  [精品线路]",  // 18
 | 
			
		||||
		"AS9808":  " 移动CMI    [普通线路]", // 18
 | 
			
		||||
		"AS58453": "移动CMI    [普通线路]",  // 18
 | 
			
		||||
	}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func removeDuplicates(elements []string) []string {
 | 
			
		||||
	encountered := map[string]bool{} // 用于存储已经遇到的元素
 | 
			
		||||
	result := []string{}             // 存储去重后的结果
 | 
			
		||||
	for v := range elements {        // 遍历切片中的元素
 | 
			
		||||
		if encountered[elements[v]] == true { // 如果该元素已经遇到过
 | 
			
		||||
			// 存在过就不加入了
 | 
			
		||||
		} else {
 | 
			
		||||
			encountered[elements[v]] = true      // 将该元素标记为已经遇到
 | 
			
		||||
			result = append(result, elements[v]) // 将该元素加入到结果切片中
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return result // 返回去重后的结果切片
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func trace(ch chan Result, i int, cmin2 []string) {
 | 
			
		||||
	hops, err := Trace(net.ParseIP(ips[i]))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		s := fmt.Sprintf("%v %-15s %v", names[i], ips[i], err)
 | 
			
		||||
		ch <- Result{i, s}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	var asns []string
 | 
			
		||||
	for _, h := range hops {
 | 
			
		||||
		for _, n := range h.Nodes {
 | 
			
		||||
			asn := ipAsn(n.IP.String(), cmin2)
 | 
			
		||||
			if asn != "" {
 | 
			
		||||
				asns = append(asns, asn)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// 处理CN2不同路线的区别
 | 
			
		||||
	if asns != nil && len(asns) > 0 {
 | 
			
		||||
		var tempText string
 | 
			
		||||
		asns = removeDuplicates(asns)
 | 
			
		||||
		tempText += fmt.Sprintf("%v ", names[i])
 | 
			
		||||
		hasAS4134 := false
 | 
			
		||||
		hasAS4809 := false
 | 
			
		||||
		for _, asn := range asns {
 | 
			
		||||
			if asn == "AS4134" {
 | 
			
		||||
				hasAS4134 = true
 | 
			
		||||
			}
 | 
			
		||||
			if asn == "AS4809" {
 | 
			
		||||
				hasAS4809 = true
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if hasAS4134 && hasAS4809 {
 | 
			
		||||
			// 同时包含 AS4134 和 AS4809 属于 CN2GT
 | 
			
		||||
			asns = append(asns, "AS4809b")
 | 
			
		||||
		} else if hasAS4809 {
 | 
			
		||||
			// 仅包含 AS4809 属于 CN2GIA
 | 
			
		||||
			asns = append(asns, "AS4809a")
 | 
			
		||||
		}
 | 
			
		||||
		tempText += fmt.Sprintf("%v %-15s ", names[i], ips[i])
 | 
			
		||||
		for _, asn := range asns {
 | 
			
		||||
			asnDescription := m[asn]
 | 
			
		||||
			switch asn {
 | 
			
		||||
			case "":
 | 
			
		||||
				continue
 | 
			
		||||
			case "AS9929":
 | 
			
		||||
				tempText += DarkGreen(asnDescription) + " "
 | 
			
		||||
			case "AS4809a":
 | 
			
		||||
				tempText += DarkGreen(asnDescription) + " "
 | 
			
		||||
			case "AS4809b":
 | 
			
		||||
				tempText += Green(asnDescription) + " "
 | 
			
		||||
			case "AS58807":
 | 
			
		||||
				tempText += Green(asnDescription) + " "
 | 
			
		||||
			default:
 | 
			
		||||
				tempText += White(asnDescription) + " "
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		ch <- Result{i, tempText}
 | 
			
		||||
	} else {
 | 
			
		||||
		s := fmt.Sprintf("%v %-15s %v", names[i], ips[i], Red("检测不到ASN"))
 | 
			
		||||
		ch <- Result{i, s}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ipAsn(ip string, cmin2 []string) string {
 | 
			
		||||
	switch {
 | 
			
		||||
	case strings.HasPrefix(ip, "59.43"):
 | 
			
		||||
		return "AS4809"
 | 
			
		||||
	case strings.HasPrefix(ip, "202.97"):
 | 
			
		||||
		return "AS4134"
 | 
			
		||||
	case strings.HasPrefix(ip, "218.105") || strings.HasPrefix(ip, "210.51"):
 | 
			
		||||
		return "AS9929"
 | 
			
		||||
	case strings.HasPrefix(ip, "219.158"):
 | 
			
		||||
		return "AS4837"
 | 
			
		||||
	case strings.HasPrefix(ip, "223.118") || strings.HasPrefix(ip, "223.119") ||
 | 
			
		||||
		strings.HasPrefix(ip, "223.120"):
 | 
			
		||||
		for _, prefix := range cmin2 {
 | 
			
		||||
			if strings.HasPrefix(ip, prefix) {
 | 
			
		||||
				return "AS58807"
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return "AS58453"
 | 
			
		||||
	case strings.HasPrefix(ip, "103.11.109") || strings.HasPrefix(ip, "45.204.69") ||
 | 
			
		||||
		strings.HasPrefix(ip, "223.121"):
 | 
			
		||||
		return "AS58453"
 | 
			
		||||
	default:
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										81
									
								
								backtrace/backtrace.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								backtrace/backtrace.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,81 @@
 | 
			
		||||
package backtrace
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	. "github.com/oneclickvirt/backtrace/defaultset"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type IpInfo struct {
 | 
			
		||||
	Ip      string `json:"ip"`
 | 
			
		||||
	City    string `json:"city"`
 | 
			
		||||
	Region  string `json:"region"`
 | 
			
		||||
	Country string `json:"country"`
 | 
			
		||||
	Org     string `json:"org"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BackTrace() {
 | 
			
		||||
	var (
 | 
			
		||||
		s     [12]string // 对应 ips 目标地址数量
 | 
			
		||||
		c     = make(chan Result)
 | 
			
		||||
		t     = time.After(time.Second * 10)
 | 
			
		||||
		cmin2 = []string{
 | 
			
		||||
			// 以下均为 /24 地址
 | 
			
		||||
			"223.118.32",
 | 
			
		||||
			"223.119.32", "223.119.34", "223.119.35", "223.119.36", "223.119.37", "223.119.100", "223.119.253",
 | 
			
		||||
			"223.120.165"}
 | 
			
		||||
		prefixes = []string{
 | 
			
		||||
			"223.119.8.0/21",
 | 
			
		||||
			"223.120.128.0/17",
 | 
			
		||||
			"223.120.134/23",
 | 
			
		||||
			"223.120.136/23",
 | 
			
		||||
			"223.120.138/23",
 | 
			
		||||
			"223.120.154/23",
 | 
			
		||||
			"223.120.158/23",
 | 
			
		||||
			"223.120.164/22",
 | 
			
		||||
			"223.120.168/22",
 | 
			
		||||
			"223.120.172/22",
 | 
			
		||||
			"223.120.174/23",
 | 
			
		||||
			"223.120.184/22",
 | 
			
		||||
			"223.120.188/22",
 | 
			
		||||
			"223.120.192/23",
 | 
			
		||||
			"223.120.200/23",
 | 
			
		||||
			"223.120.210/23",
 | 
			
		||||
			"223.120.212/23",
 | 
			
		||||
		}
 | 
			
		||||
	)
 | 
			
		||||
	// 生成CMIN2的IPV4前缀地址
 | 
			
		||||
	for _, prefix := range prefixes {
 | 
			
		||||
		cmin2 = append(cmin2, GeneratePrefixList(prefix)...)
 | 
			
		||||
	}
 | 
			
		||||
	rsp, err := http.Get("http://ipinfo.io")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatalln("Get ip info err", err)
 | 
			
		||||
	}
 | 
			
		||||
	info := IpInfo{}
 | 
			
		||||
	err = json.NewDecoder(rsp.Body).Decode(&info)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatalln("json decode err", err)
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Println(Green("国家: ") + White(info.Country) + Green(" 城市: ") + White(info.City) +
 | 
			
		||||
		Green(" 服务商: ") + Blue(info.Org))
 | 
			
		||||
	for i := range ips {
 | 
			
		||||
		go trace(c, i, cmin2)
 | 
			
		||||
	}
 | 
			
		||||
loop:
 | 
			
		||||
	for range s {
 | 
			
		||||
		select {
 | 
			
		||||
		case o := <-c:
 | 
			
		||||
			s[o.i] = o.s
 | 
			
		||||
		case <-t:
 | 
			
		||||
			break loop
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, r := range s {
 | 
			
		||||
		fmt.Println(r)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										20
									
								
								backtrace/backtrace_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								backtrace/backtrace_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
			
		||||
package backtrace
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
//func TestGeneratePrefixMap(t *testing.T) {
 | 
			
		||||
//	prefix := "223.119.8.0/21"
 | 
			
		||||
//	prefixList := GeneratePrefixList(prefix)
 | 
			
		||||
//	if prefixList != nil {
 | 
			
		||||
//		// 打印生成的IP地址前缀列表
 | 
			
		||||
//		for _, ip := range prefixList {
 | 
			
		||||
//			fmt.Println(ip)
 | 
			
		||||
//		}
 | 
			
		||||
//	}
 | 
			
		||||
//}
 | 
			
		||||
 | 
			
		||||
func TestBackTrace(t *testing.T) {
 | 
			
		||||
	BackTrace()
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										38
									
								
								backtrace/generate.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								backtrace/generate.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,38 @@
 | 
			
		||||
package backtrace
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// GeneratePrefixList 生成指定前缀的IP地址列表
 | 
			
		||||
func GeneratePrefixList(prefix string) []string {
 | 
			
		||||
	// 解析CIDR表示法的IP地址
 | 
			
		||||
	_, ipNet, err := net.ParseCIDR(prefix)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	// 获取IP地址的32位整数表示
 | 
			
		||||
	ip := ipNet.IP.To4()
 | 
			
		||||
	start := binaryIPToInt(ip)
 | 
			
		||||
	maskSize, _ := ipNet.Mask.Size()
 | 
			
		||||
	end := start | (1<<(32-maskSize) - 1)
 | 
			
		||||
	// 生成IP地址列表
 | 
			
		||||
	var prefixList []string
 | 
			
		||||
	for i := start; i <= end; i++ {
 | 
			
		||||
		if (i-start)%256 == 0 {
 | 
			
		||||
			tempText := intToBinaryIP(i).String()
 | 
			
		||||
			prefixList = append(prefixList, tempText[:len(tempText)-2])
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return prefixList
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 将IP地址转换为32位整数
 | 
			
		||||
func binaryIPToInt(ip net.IP) uint32 {
 | 
			
		||||
	return (uint32(ip[0]) << 24) | (uint32(ip[1]) << 16) | (uint32(ip[2]) << 8) | uint32(ip[3])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 将32位整数转换为IP地址
 | 
			
		||||
func intToBinaryIP(i uint32) net.IP {
 | 
			
		||||
	return net.IPv4(byte(i>>24), byte(i>>16), byte(i>>8), byte(i))
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										29
									
								
								backtrace/listen_linux.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								backtrace/listen_linux.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,29 @@
 | 
			
		||||
//go:build linux
 | 
			
		||||
// +build linux
 | 
			
		||||
 | 
			
		||||
package backtrace
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net"
 | 
			
		||||
	"syscall"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (t *Tracer) listen(network string, laddr *net.IPAddr) (*net.IPConn, error) {
 | 
			
		||||
	conn, err := net.ListenIP(network, laddr)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	raw, err := conn.SyscallConn()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		conn.Close()
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	_ = raw.Control(func(fd uintptr) {
 | 
			
		||||
		err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_HDRINCL, 1)
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		conn.Close()
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return conn, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										35
									
								
								backtrace/listen_windows.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								backtrace/listen_windows.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
			
		||||
//go:build windows
 | 
			
		||||
// +build windows
 | 
			
		||||
 | 
			
		||||
package backtrace
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	. "github.com/oneclickvirt/backtrace/defaultset"
 | 
			
		||||
	"golang.org/x/sys/windows"
 | 
			
		||||
	"net"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (t *Tracer) listen(network string, laddr *net.IPAddr) (*net.IPConn, error) {
 | 
			
		||||
	InitLogger()
 | 
			
		||||
	defer Logger.Sync()
 | 
			
		||||
	conn, err := net.ListenIP(network, laddr)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		Logger.Info(err.Error())
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	raw, err := conn.SyscallConn()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		Logger.Info(err.Error())
 | 
			
		||||
		conn.Close()
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	_ = raw.Control(func(fd uintptr) {
 | 
			
		||||
		err = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IP, windows.IP_HDRINCL, 1)
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		Logger.Info(err.Error())
 | 
			
		||||
		conn.Close()
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return conn, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										467
									
								
								backtrace/trace.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										467
									
								
								backtrace/trace.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,467 @@
 | 
			
		||||
package backtrace
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"golang.org/x/net/icmp"
 | 
			
		||||
	"golang.org/x/net/ipv4"
 | 
			
		||||
	"golang.org/x/net/ipv6"
 | 
			
		||||
	"net"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"sync/atomic"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// DefaultConfig is the default configuration for Tracer.
 | 
			
		||||
var DefaultConfig = Config{
 | 
			
		||||
	Delay:    50 * time.Millisecond,
 | 
			
		||||
	Timeout:  500 * time.Millisecond,
 | 
			
		||||
	MaxHops:  15,
 | 
			
		||||
	Count:    1,
 | 
			
		||||
	Networks: []string{"ip4:icmp", "ip4:ip"},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DefaultTracer is a tracer with DefaultConfig.
 | 
			
		||||
var DefaultTracer = &Tracer{
 | 
			
		||||
	Config: DefaultConfig,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Config is a configuration for Tracer.
 | 
			
		||||
type Config struct {
 | 
			
		||||
	Delay    time.Duration
 | 
			
		||||
	Timeout  time.Duration
 | 
			
		||||
	MaxHops  int
 | 
			
		||||
	Count    int
 | 
			
		||||
	Networks []string
 | 
			
		||||
	Addr     *net.IPAddr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Tracer is a traceroute tool based on raw IP packets.
 | 
			
		||||
// It can handle multiple sessions simultaneously.
 | 
			
		||||
type Tracer struct {
 | 
			
		||||
	Config
 | 
			
		||||
 | 
			
		||||
	once sync.Once
 | 
			
		||||
	conn *net.IPConn
 | 
			
		||||
	err  error
 | 
			
		||||
 | 
			
		||||
	mu   sync.RWMutex
 | 
			
		||||
	sess map[string][]*Session
 | 
			
		||||
	seq  uint32
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Trace starts sending IP packets increasing TTL until MaxHops and calls h for each reply.
 | 
			
		||||
func (t *Tracer) Trace(ctx context.Context, ip net.IP, h func(reply *Reply)) error {
 | 
			
		||||
	sess, err := t.NewSession(ip)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer sess.Close()
 | 
			
		||||
 | 
			
		||||
	delay := time.NewTicker(t.Delay)
 | 
			
		||||
	defer delay.Stop()
 | 
			
		||||
 | 
			
		||||
	max := t.MaxHops
 | 
			
		||||
	for n := 0; n < t.Count; n++ {
 | 
			
		||||
		for ttl := 1; ttl <= t.MaxHops && ttl <= max; ttl++ {
 | 
			
		||||
			err = sess.Ping(ttl)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			select {
 | 
			
		||||
			case <-delay.C:
 | 
			
		||||
			case r := <-sess.Receive():
 | 
			
		||||
				if max > r.Hops && ip.Equal(r.IP) {
 | 
			
		||||
					max = r.Hops
 | 
			
		||||
				}
 | 
			
		||||
				h(r)
 | 
			
		||||
			case <-ctx.Done():
 | 
			
		||||
				return ctx.Err()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if sess.isDone(max) {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	deadline := time.After(t.Timeout)
 | 
			
		||||
	for {
 | 
			
		||||
		select {
 | 
			
		||||
		case r := <-sess.Receive():
 | 
			
		||||
			if max > r.Hops && ip.Equal(r.IP) {
 | 
			
		||||
				max = r.Hops
 | 
			
		||||
			}
 | 
			
		||||
			h(r)
 | 
			
		||||
			if sess.isDone(max) {
 | 
			
		||||
				return nil
 | 
			
		||||
			}
 | 
			
		||||
		case <-deadline:
 | 
			
		||||
			return nil
 | 
			
		||||
		case <-ctx.Done():
 | 
			
		||||
			return ctx.Err()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewSession returns new tracer session.
 | 
			
		||||
func (t *Tracer) NewSession(ip net.IP) (*Session, error) {
 | 
			
		||||
	t.once.Do(t.init)
 | 
			
		||||
	if t.err != nil {
 | 
			
		||||
		return nil, t.err
 | 
			
		||||
	}
 | 
			
		||||
	return newSession(t, shortIP(ip)), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *Tracer) init() {
 | 
			
		||||
	for _, network := range t.Networks {
 | 
			
		||||
		t.conn, t.err = t.listen(network, t.Addr)
 | 
			
		||||
		if t.err != nil {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		go t.serve(t.conn)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Close closes listening socket.
 | 
			
		||||
// Tracer can not be used after Close is called.
 | 
			
		||||
func (t *Tracer) Close() {
 | 
			
		||||
	t.mu.Lock()
 | 
			
		||||
	defer t.mu.Unlock()
 | 
			
		||||
	if t.conn != nil {
 | 
			
		||||
		t.conn.Close()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *Tracer) serve(conn *net.IPConn) error {
 | 
			
		||||
	defer conn.Close()
 | 
			
		||||
	buf := make([]byte, 1500)
 | 
			
		||||
	for {
 | 
			
		||||
		n, from, err := conn.ReadFromIP(buf)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		err = t.serveData(from.IP, buf[:n])
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *Tracer) serveData(from net.IP, b []byte) error {
 | 
			
		||||
	if from.To4() == nil {
 | 
			
		||||
		// TODO: implement ProtocolIPv6ICMP
 | 
			
		||||
		return errUnsupportedProtocol
 | 
			
		||||
	}
 | 
			
		||||
	now := time.Now()
 | 
			
		||||
	msg, err := icmp.ParseMessage(ProtocolICMP, b)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if msg.Type == ipv4.ICMPTypeEchoReply {
 | 
			
		||||
		echo := msg.Body.(*icmp.Echo)
 | 
			
		||||
		return t.serveReply(from, &packet{from, uint16(echo.ID), 1, now})
 | 
			
		||||
	}
 | 
			
		||||
	b = getReplyData(msg)
 | 
			
		||||
	if len(b) < ipv4.HeaderLen {
 | 
			
		||||
		return errMessageTooShort
 | 
			
		||||
	}
 | 
			
		||||
	switch b[0] >> 4 {
 | 
			
		||||
	case ipv4.Version:
 | 
			
		||||
		ip, err := ipv4.ParseHeader(b)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		return t.serveReply(ip.Dst, &packet{from, uint16(ip.ID), ip.TTL, now})
 | 
			
		||||
	case ipv6.Version:
 | 
			
		||||
		ip, err := ipv6.ParseHeader(b)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		return t.serveReply(ip.Dst, &packet{from, uint16(ip.FlowLabel), ip.HopLimit, now})
 | 
			
		||||
	default:
 | 
			
		||||
		return errUnsupportedProtocol
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *Tracer) sendRequest(dst net.IP, ttl int) (*packet, error) {
 | 
			
		||||
	id := uint16(atomic.AddUint32(&t.seq, 1))
 | 
			
		||||
	b := newPacket(id, dst, ttl)
 | 
			
		||||
	req := &packet{dst, id, ttl, time.Now()}
 | 
			
		||||
	_, err := t.conn.WriteToIP(b, &net.IPAddr{IP: dst})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return req, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *Tracer) addSession(s *Session) {
 | 
			
		||||
	t.mu.Lock()
 | 
			
		||||
	defer t.mu.Unlock()
 | 
			
		||||
	if t.sess == nil {
 | 
			
		||||
		t.sess = make(map[string][]*Session)
 | 
			
		||||
	}
 | 
			
		||||
	t.sess[string(s.ip)] = append(t.sess[string(s.ip)], s)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *Tracer) removeSession(s *Session) {
 | 
			
		||||
	t.mu.Lock()
 | 
			
		||||
	defer t.mu.Unlock()
 | 
			
		||||
	a := t.sess[string(s.ip)]
 | 
			
		||||
	for i, it := range a {
 | 
			
		||||
		if it == s {
 | 
			
		||||
			t.sess[string(s.ip)] = append(a[:i], a[i+1:]...)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *Tracer) serveReply(dst net.IP, res *packet) error {
 | 
			
		||||
	t.mu.RLock()
 | 
			
		||||
	defer t.mu.RUnlock()
 | 
			
		||||
	a := t.sess[string(shortIP(dst))]
 | 
			
		||||
	for _, s := range a {
 | 
			
		||||
		s.handle(res)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Session is a tracer session.
 | 
			
		||||
type Session struct {
 | 
			
		||||
	t  *Tracer
 | 
			
		||||
	ip net.IP
 | 
			
		||||
	ch chan *Reply
 | 
			
		||||
 | 
			
		||||
	mu     sync.RWMutex
 | 
			
		||||
	probes []*packet
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewSession returns new session.
 | 
			
		||||
func NewSession(ip net.IP) (*Session, error) {
 | 
			
		||||
	return DefaultTracer.NewSession(ip)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newSession(t *Tracer, ip net.IP) *Session {
 | 
			
		||||
	s := &Session{
 | 
			
		||||
		t:  t,
 | 
			
		||||
		ip: ip,
 | 
			
		||||
		ch: make(chan *Reply, 64),
 | 
			
		||||
	}
 | 
			
		||||
	t.addSession(s)
 | 
			
		||||
	return s
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Ping sends single ICMP packet with specified TTL.
 | 
			
		||||
func (s *Session) Ping(ttl int) error {
 | 
			
		||||
	req, err := s.t.sendRequest(s.ip, ttl+1)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	s.mu.Lock()
 | 
			
		||||
	s.probes = append(s.probes, req)
 | 
			
		||||
	s.mu.Unlock()
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Receive returns channel to receive ICMP replies.
 | 
			
		||||
func (s *Session) Receive() <-chan *Reply {
 | 
			
		||||
	return s.ch
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// isDone returns true if session does not have unresponsed requests with TTL <= ttl.
 | 
			
		||||
func (s *Session) isDone(ttl int) bool {
 | 
			
		||||
	s.mu.RLock()
 | 
			
		||||
	defer s.mu.RUnlock()
 | 
			
		||||
	for _, r := range s.probes {
 | 
			
		||||
		if r.TTL <= ttl {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *Session) handle(res *packet) {
 | 
			
		||||
	now := res.Time
 | 
			
		||||
	n := 0
 | 
			
		||||
	var req *packet
 | 
			
		||||
	s.mu.Lock()
 | 
			
		||||
	for _, r := range s.probes {
 | 
			
		||||
		if now.Sub(r.Time) > s.t.Timeout {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if r.ID == res.ID {
 | 
			
		||||
			req = r
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		s.probes[n] = r
 | 
			
		||||
		n++
 | 
			
		||||
	}
 | 
			
		||||
	s.probes = s.probes[:n]
 | 
			
		||||
	s.mu.Unlock()
 | 
			
		||||
	if req == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	hops := req.TTL - res.TTL + 1
 | 
			
		||||
	if hops < 1 {
 | 
			
		||||
		hops = 1
 | 
			
		||||
	}
 | 
			
		||||
	select {
 | 
			
		||||
	case s.ch <- &Reply{
 | 
			
		||||
		IP:   res.IP,
 | 
			
		||||
		RTT:  res.Time.Sub(req.Time),
 | 
			
		||||
		Hops: hops,
 | 
			
		||||
	}:
 | 
			
		||||
	default:
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Close closes tracer session.
 | 
			
		||||
func (s *Session) Close() {
 | 
			
		||||
	s.t.removeSession(s)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type packet struct {
 | 
			
		||||
	IP   net.IP
 | 
			
		||||
	ID   uint16
 | 
			
		||||
	TTL  int
 | 
			
		||||
	Time time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func shortIP(ip net.IP) net.IP {
 | 
			
		||||
	if v := ip.To4(); v != nil {
 | 
			
		||||
		return v
 | 
			
		||||
	}
 | 
			
		||||
	return ip
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getReplyData(msg *icmp.Message) []byte {
 | 
			
		||||
	switch b := msg.Body.(type) {
 | 
			
		||||
	case *icmp.TimeExceeded:
 | 
			
		||||
		return b.Data
 | 
			
		||||
	case *icmp.DstUnreach:
 | 
			
		||||
		return b.Data
 | 
			
		||||
	case *icmp.ParamProb:
 | 
			
		||||
		return b.Data
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	errMessageTooShort     = errors.New("message too short")
 | 
			
		||||
	errUnsupportedProtocol = errors.New("unsupported protocol")
 | 
			
		||||
	errNoReplyData         = errors.New("no reply data")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func newPacket(id uint16, dst net.IP, ttl int) []byte {
 | 
			
		||||
	// TODO: reuse buffers...
 | 
			
		||||
	msg := icmp.Message{
 | 
			
		||||
		Type: ipv4.ICMPTypeEcho,
 | 
			
		||||
		Body: &icmp.Echo{
 | 
			
		||||
			ID:  int(id),
 | 
			
		||||
			Seq: int(id),
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	p, _ := msg.Marshal(nil)
 | 
			
		||||
	ip := &ipv4.Header{
 | 
			
		||||
		Version:  ipv4.Version,
 | 
			
		||||
		Len:      ipv4.HeaderLen,
 | 
			
		||||
		TotalLen: ipv4.HeaderLen + len(p),
 | 
			
		||||
		TOS:      16,
 | 
			
		||||
		ID:       int(id),
 | 
			
		||||
		Dst:      dst,
 | 
			
		||||
		Protocol: ProtocolICMP,
 | 
			
		||||
		TTL:      ttl,
 | 
			
		||||
	}
 | 
			
		||||
	buf, err := ip.Marshal()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return append(buf, p...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IANA Assigned Internet Protocol Numbers
 | 
			
		||||
const (
 | 
			
		||||
	ProtocolICMP     = 1
 | 
			
		||||
	ProtocolTCP      = 6
 | 
			
		||||
	ProtocolUDP      = 17
 | 
			
		||||
	ProtocolIPv6ICMP = 58
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Reply is a reply packet.
 | 
			
		||||
type Reply struct {
 | 
			
		||||
	IP   net.IP
 | 
			
		||||
	RTT  time.Duration
 | 
			
		||||
	Hops int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Node is a detected network node.
 | 
			
		||||
type Node struct {
 | 
			
		||||
	IP  net.IP
 | 
			
		||||
	RTT []time.Duration
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Hop is a set of detected nodes.
 | 
			
		||||
type Hop struct {
 | 
			
		||||
	Nodes    []*Node
 | 
			
		||||
	Distance int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Add adds node from r.
 | 
			
		||||
func (h *Hop) Add(r *Reply) *Node {
 | 
			
		||||
	var node *Node
 | 
			
		||||
	for _, it := range h.Nodes {
 | 
			
		||||
		if it.IP.Equal(r.IP) {
 | 
			
		||||
			node = it
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if node == nil {
 | 
			
		||||
		node = &Node{IP: r.IP}
 | 
			
		||||
		h.Nodes = append(h.Nodes, node)
 | 
			
		||||
	}
 | 
			
		||||
	node.RTT = append(node.RTT, r.RTT)
 | 
			
		||||
	return node
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Trace is a simple traceroute tool using DefaultTracer.
 | 
			
		||||
func Trace(ip net.IP) ([]*Hop, error) {
 | 
			
		||||
	hops := make([]*Hop, 0, DefaultTracer.MaxHops)
 | 
			
		||||
	touch := func(dist int) *Hop {
 | 
			
		||||
		for _, h := range hops {
 | 
			
		||||
			if h.Distance == dist {
 | 
			
		||||
				return h
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		h := &Hop{Distance: dist}
 | 
			
		||||
		hops = append(hops, h)
 | 
			
		||||
		return h
 | 
			
		||||
	}
 | 
			
		||||
	err := DefaultTracer.Trace(context.Background(), ip, func(r *Reply) {
 | 
			
		||||
		touch(r.Hops).Add(r)
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil && err != context.DeadlineExceeded {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	sort.Slice(hops, func(i, j int) bool {
 | 
			
		||||
		return hops[i].Distance < hops[j].Distance
 | 
			
		||||
	})
 | 
			
		||||
	last := len(hops) - 1
 | 
			
		||||
	for i := last; i >= 0; i-- {
 | 
			
		||||
		h := hops[i]
 | 
			
		||||
		if len(h.Nodes) == 1 && ip.Equal(h.Nodes[0].IP) {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if i == last {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		i++
 | 
			
		||||
		node := hops[i].Nodes[0]
 | 
			
		||||
		i++
 | 
			
		||||
		for _, it := range hops[i:] {
 | 
			
		||||
			node.RTT = append(node.RTT, it.Nodes[0].RTT...)
 | 
			
		||||
		}
 | 
			
		||||
		hops = hops[:i]
 | 
			
		||||
		break
 | 
			
		||||
	}
 | 
			
		||||
	return hops, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										35
									
								
								defaultset/defaultset.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								defaultset/defaultset.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
			
		||||
package defaultset
 | 
			
		||||
 | 
			
		||||
import "fmt"
 | 
			
		||||
 | 
			
		||||
func Red(text string) string {
 | 
			
		||||
	return fmt.Sprintf("\033[31m\033[01m%s\033[0m", text)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Green(text string) string {
 | 
			
		||||
	return fmt.Sprintf("\033[32m\033[01m%s\033[0m", text)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func DarkGreen(text string) string {
 | 
			
		||||
	return fmt.Sprintf("\033[32m\033[02m%s\033[0m", text)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Yellow(text string) string {
 | 
			
		||||
	return fmt.Sprintf("\033[33m\033[01m%s\033[0m", text)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Blue(text string) string {
 | 
			
		||||
	return fmt.Sprintf("\033[36m\033[01m%s\033[0m", text)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Purple(text string) string {
 | 
			
		||||
	return fmt.Sprintf("\033[35m\033[01m%s\033[0m", text)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Cyan(text string) string {
 | 
			
		||||
	return fmt.Sprintf("\033[36m\033[01m%s\033[0m", text)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func White(text string) string {
 | 
			
		||||
	return fmt.Sprintf("\033[37m\033[01m%s\033[0m", text)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										40
									
								
								defaultset/zap.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								defaultset/zap.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,40 @@
 | 
			
		||||
package defaultset
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"go.uber.org/zap"
 | 
			
		||||
	"go.uber.org/zap/zapcore"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var Logger *zap.Logger
 | 
			
		||||
 | 
			
		||||
func getZapConfig() zap.Config {
 | 
			
		||||
	cfg := zap.Config{
 | 
			
		||||
		Encoding:         "console",                           // 日志编码格式
 | 
			
		||||
		Level:            zap.NewAtomicLevelAt(zap.InfoLevel), // 日志级别
 | 
			
		||||
		OutputPaths:      []string{"ecs.log"},                 // 输出路径,可以是多个文件
 | 
			
		||||
		ErrorOutputPaths: []string{},                          // 错误输出路径,可以是多个文件
 | 
			
		||||
		EncoderConfig: zapcore.EncoderConfig{
 | 
			
		||||
			TimeKey:       "timestamp",                   // 时间字段名称
 | 
			
		||||
			LevelKey:      "level",                       // 日志级别字段名称
 | 
			
		||||
			NameKey:       "logger",                      // 日志记录器名称字段名称
 | 
			
		||||
			CallerKey:     "caller",                      // 调用者信息字段名称
 | 
			
		||||
			MessageKey:    "message",                     // 日志消息字段名称
 | 
			
		||||
			StacktraceKey: "stacktrace",                  // 堆栈跟踪信息字段名称
 | 
			
		||||
			EncodeLevel:   zapcore.LowercaseLevelEncoder, // 小写格式的日志级别编码器
 | 
			
		||||
			EncodeTime:    zapcore.ISO8601TimeEncoder,    // ISO8601 时间格式编码器
 | 
			
		||||
			EncodeCaller:  zapcore.ShortCallerEncoder,    // 编码短调用者信息
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	return cfg
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// InitLogger 初始化日志记录器
 | 
			
		||||
func InitLogger() {
 | 
			
		||||
	// 配置日志记录器
 | 
			
		||||
	cfg := getZapConfig()
 | 
			
		||||
	var err error
 | 
			
		||||
	Logger, err = cfg.Build()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic("failed to initialize logger: " + err.Error())
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,14 @@
 | 
			
		||||
module github.com/oneclickvirt/backtrace
 | 
			
		||||
 | 
			
		||||
go 1.21.5
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	go.uber.org/zap v1.27.0
 | 
			
		||||
	golang.org/x/net v0.24.0
 | 
			
		||||
	golang.org/x/sys v0.19.0
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/stretchr/testify v1.9.0 // indirect
 | 
			
		||||
	go.uber.org/multierr v1.11.0 // indirect
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										18
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
			
		||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
			
		||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
 | 
			
		||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 | 
			
		||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
 | 
			
		||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
 | 
			
		||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
 | 
			
		||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
 | 
			
		||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
 | 
			
		||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
 | 
			
		||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
 | 
			
		||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
 | 
			
		||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
 | 
			
		||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
			
		||||
							
								
								
									
										16
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,16 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"github.com/oneclickvirt/backtrace/backtrace"
 | 
			
		||||
	. "github.com/oneclickvirt/backtrace/defaultset"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	go func() {
 | 
			
		||||
		http.Get("https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Foneclickvirt%2Fbacktrace&count_bg=%2323E01C&title_bg=%23555555&icon=sonarcloud.svg&icon_color=%23E7E7E7&title=hits&edge_flat=false")
 | 
			
		||||
	}()
 | 
			
		||||
	fmt.Println(Green("项目地址:"), Yellow("https://github.com/oneclickvirt/backtrace"))
 | 
			
		||||
	backtrace.BackTrace()
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user