mirror of
https://github.com/oneclickvirt/backtrace.git
synced 2025-02-23 09:25:40 +08:00
init
This commit is contained in:
parent
11df342bc1
commit
8b15256f69
@ -1,2 +1,5 @@
|
|||||||
# backtrace
|
# backtrace
|
||||||
|
|
||||||
|
[](https://hits.seeyoufarm.com)
|
||||||
|
|
||||||
基于 https://github.com/zhanghanyun/backtrace 的重构和优化版本
|
基于 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