├── .github └── workflows │ ├── go.yml │ └── release.yml ├── LICENSE ├── README.md ├── asn.go ├── assets └── test.png ├── go.mod ├── go.sum ├── install.sh ├── main.go └── trace.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v3 18 | with: 19 | go-version: 'stable' 20 | 21 | - name: Build 22 | run: go build -v ./... 23 | 24 | - name: Test 25 | run: go test -v ./... 26 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | releases-matrix: 9 | name: Release Go Binary 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | goos: [linux] 14 | goarch: [amd64, arm64] 15 | steps: 16 | - uses: actions/checkout@v3 17 | - uses: wangyoucao577/go-release-action@v1 18 | with: 19 | github_token: ${{ secrets.GITHUB_TOKEN }} 20 | goos: ${{ matrix.goos }} 21 | goarch: ${{ matrix.goarch }} 22 | asset_name: backtrace-${{ matrix.goos }}-${{ matrix.goarch }} 23 | ldflags: -s -w -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 zhy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 | ## 三网回程路由测试 8 | ![](https://raw.githubusercontent.com/zhanghanyun/backtrace/main/assets/test.png) 9 | 10 | ## 使用 11 | 终端下运行 12 | ```shell 13 | curl https://raw.githubusercontent.com/zhanghanyun/backtrace/main/install.sh -sSf | sh 14 | ``` 15 | -------------------------------------------------------------------------------- /asn.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/fatih/color" 6 | "net" 7 | "strings" 8 | ) 9 | 10 | type Result struct { 11 | i int 12 | s string 13 | } 14 | 15 | var ( 16 | ips = []string{"219.141.140.10", "202.106.195.68", "221.179.155.161", "202.96.209.133", "210.22.97.1", 17 | "211.136.112.200", "58.60.188.222", "210.21.196.6", "120.196.165.24", "61.139.2.69", "119.6.6.6", 18 | "211.137.96.205"} 19 | names = []string{"北京电信", "北京联通", "北京移动", "上海电信", "上海联通", "上海移动", "广州电信", "广州联通", "广州移动", 20 | "成都电信", "成都联通", "成都移动"} 21 | m = map[string]string{"AS4134": "电信163 [普通线路]", "AS4809": "电信CN2 [优质线路]", "AS4837": "联通4837 [普通线路]", 22 | "AS9929": "联通9929 [优质线路]", "AS58807": "移动CMIN2[优质线路]", "AS9808": "移动CMI [普通线路]", "AS58453": "移动CMI [普通线路]"} 23 | ) 24 | 25 | func trace(ch chan Result, i int) { 26 | 27 | hops, err := Trace(net.ParseIP(ips[i])) 28 | if err != nil { 29 | s := fmt.Sprintf("%v %-15s %v", names[i], ips[i], err) 30 | ch <- Result{i, s} 31 | return 32 | } 33 | 34 | for _, h := range hops { 35 | for _, n := range h.Nodes { 36 | asn := ipAsn(n.IP.String()) 37 | as := m[asn] 38 | var c func(a ...interface{}) string 39 | switch asn { 40 | case "": 41 | continue 42 | case "AS9929": 43 | c = color.New(color.FgHiYellow).Add(color.Bold).SprintFunc() 44 | case "AS4809": 45 | c = color.New(color.FgHiMagenta).Add(color.Bold).SprintFunc() 46 | case "AS58807": 47 | c = color.New(color.FgHiBlue).Add(color.Bold).SprintFunc() 48 | default: 49 | c = color.New(color.FgWhite).Add(color.Bold).SprintFunc() 50 | } 51 | 52 | s := fmt.Sprintf("%v %-15s %-23s", names[i], ips[i], c(as)) 53 | ch <- Result{i, s} 54 | return 55 | } 56 | } 57 | c := color.New(color.FgRed).Add(color.Bold).SprintFunc() 58 | s := fmt.Sprintf("%v %-15s %v", names[i], ips[i], c("测试超时")) 59 | ch <- Result{i, s} 60 | } 61 | 62 | func ipAsn(ip string) string { 63 | 64 | switch { 65 | case strings.HasPrefix(ip, "59.43"): 66 | return "AS4809" 67 | case strings.HasPrefix(ip, "202.97"): 68 | return "AS4134" 69 | case strings.HasPrefix(ip, "218.105") || strings.HasPrefix(ip, "210.51"): 70 | return "AS9929" 71 | case strings.HasPrefix(ip, "219.158"): 72 | return "AS4837" 73 | case strings.HasPrefix(ip, "223.120.19") || strings.HasPrefix(ip, "223.120.17") || strings.HasPrefix(ip, "223.120.16"): 74 | return "AS58807" 75 | case strings.HasPrefix(ip, "223.118") || strings.HasPrefix(ip, "223.119") || strings.HasPrefix(ip, "223.120") || strings.HasPrefix(ip, "223.121"): 76 | return "AS58453" 77 | default: 78 | return "" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /assets/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghanyun/backtrace/556fecd573ce5c687df0a6fdb6d13d85563f0ba6/assets/test.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module backtrace 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/fatih/color v1.17.0 7 | golang.org/x/net v0.25.0 8 | ) 9 | 10 | require ( 11 | github.com/mattn/go-colorable v0.1.13 // indirect 12 | github.com/mattn/go-isatty v0.0.20 // indirect 13 | golang.org/x/sys v0.20.0 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= 2 | github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= 3 | github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= 4 | github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= 5 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 6 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 7 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 8 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= 9 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 10 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 11 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 12 | golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= 13 | golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= 14 | golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= 15 | golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= 16 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 17 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 18 | golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= 19 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 20 | golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= 21 | golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 22 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | arch=$(uname -m) 4 | if [ "$arch" = "x86_64" ]; then 5 | wget -q -O backtrace.tar.gz https://github.com/zhanghanyun/backtrace/releases/latest/download/backtrace-linux-amd64.tar.gz 6 | else 7 | wget -q -O backtrace.tar.gz https://github.com/zhanghanyun/backtrace/releases/latest/download/backtrace-linux-arm64.tar.gz 8 | fi 9 | 10 | tar -xf backtrace.tar.gz 11 | rm backtrace.tar.gz 12 | mv backtrace /usr/bin/ 13 | backtrace -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/fatih/color" 7 | "log" 8 | "net/http" 9 | "time" 10 | ) 11 | 12 | type IpInfo struct { 13 | Ip string `json:"ip"` 14 | City string `json:"city"` 15 | Region string `json:"region"` 16 | Country string `json:"country"` 17 | Org string `json:"org"` 18 | } 19 | 20 | func main() { 21 | 22 | var ( 23 | s [12]string 24 | c = make(chan Result) 25 | t = time.After(time.Second * 10) 26 | ) 27 | 28 | go func() { 29 | http.Get("https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fzhanghanyun%2Fbacktrace&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false") 30 | }() 31 | 32 | yellow := color.New(color.FgHiYellow).Add(color.Bold).SprintFunc() 33 | green := color.New(color.FgHiGreen).SprintFunc() 34 | cyan := color.New(color.FgHiCyan).SprintFunc() 35 | log.Println("正在测试三网回程路由...") 36 | 37 | rsp, _ := http.Get("http://ipinfo.io") 38 | info := IpInfo{} 39 | json.NewDecoder(rsp.Body).Decode(&info) 40 | 41 | fmt.Println(green("国家: ") + cyan(info.Country) + green(" 城市: ") + cyan(info.City) + green(" 服务商: ") + cyan(info.Org)) 42 | fmt.Println(green("项目地址:"), yellow("https://github.com/zhanghanyun/backtrace")) 43 | 44 | for i := range ips { 45 | go trace(c, i) 46 | } 47 | 48 | loop: 49 | for range s { 50 | select { 51 | case o := <-c: 52 | s[o.i] = o.s 53 | case <-t: 54 | break loop 55 | } 56 | } 57 | 58 | for _, r := range s { 59 | fmt.Println(r) 60 | } 61 | log.Println(green("测试完成!")) 62 | } 63 | -------------------------------------------------------------------------------- /trace.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "golang.org/x/net/icmp" 7 | "golang.org/x/net/ipv4" 8 | "golang.org/x/net/ipv6" 9 | "net" 10 | "sort" 11 | "sync" 12 | "sync/atomic" 13 | "syscall" 14 | "time" 15 | ) 16 | 17 | // DefaultConfig is the default configuration for Tracer. 18 | var DefaultConfig = Config{ 19 | Delay: 50 * time.Millisecond, 20 | Timeout: 500 * time.Millisecond, 21 | MaxHops: 15, 22 | Count: 1, 23 | Networks: []string{"ip4:icmp", "ip4:ip"}, 24 | } 25 | 26 | // DefaultTracer is a tracer with DefaultConfig. 27 | var DefaultTracer = &Tracer{ 28 | Config: DefaultConfig, 29 | } 30 | 31 | // Config is a configuration for Tracer. 32 | type Config struct { 33 | Delay time.Duration 34 | Timeout time.Duration 35 | MaxHops int 36 | Count int 37 | Networks []string 38 | Addr *net.IPAddr 39 | } 40 | 41 | // Tracer is a traceroute tool based on raw IP packets. 42 | // It can handle multiple sessions simultaneously. 43 | type Tracer struct { 44 | Config 45 | 46 | once sync.Once 47 | conn *net.IPConn 48 | err error 49 | 50 | mu sync.RWMutex 51 | sess map[string][]*Session 52 | seq uint32 53 | } 54 | 55 | // Trace starts sending IP packets increasing TTL until MaxHops and calls h for each reply. 56 | func (t *Tracer) Trace(ctx context.Context, ip net.IP, h func(reply *Reply)) error { 57 | sess, err := t.NewSession(ip) 58 | if err != nil { 59 | return err 60 | } 61 | defer sess.Close() 62 | 63 | delay := time.NewTicker(t.Delay) 64 | defer delay.Stop() 65 | 66 | max := t.MaxHops 67 | for n := 0; n < t.Count; n++ { 68 | for ttl := 1; ttl <= t.MaxHops && ttl <= max; ttl++ { 69 | err = sess.Ping(ttl) 70 | if err != nil { 71 | return err 72 | } 73 | select { 74 | case <-delay.C: 75 | case r := <-sess.Receive(): 76 | if max > r.Hops && ip.Equal(r.IP) { 77 | max = r.Hops 78 | } 79 | h(r) 80 | case <-ctx.Done(): 81 | return ctx.Err() 82 | } 83 | } 84 | } 85 | if sess.isDone(max) { 86 | return nil 87 | } 88 | deadline := time.After(t.Timeout) 89 | for { 90 | select { 91 | case r := <-sess.Receive(): 92 | if max > r.Hops && ip.Equal(r.IP) { 93 | max = r.Hops 94 | } 95 | h(r) 96 | if sess.isDone(max) { 97 | return nil 98 | } 99 | case <-deadline: 100 | return nil 101 | case <-ctx.Done(): 102 | return ctx.Err() 103 | } 104 | } 105 | } 106 | 107 | // NewSession returns new tracer session. 108 | func (t *Tracer) NewSession(ip net.IP) (*Session, error) { 109 | t.once.Do(t.init) 110 | if t.err != nil { 111 | return nil, t.err 112 | } 113 | return newSession(t, shortIP(ip)), nil 114 | } 115 | 116 | func (t *Tracer) init() { 117 | for _, network := range t.Networks { 118 | t.conn, t.err = t.listen(network, t.Addr) 119 | if t.err != nil { 120 | continue 121 | } 122 | go t.serve(t.conn) 123 | return 124 | } 125 | } 126 | 127 | func (t *Tracer) listen(network string, laddr *net.IPAddr) (*net.IPConn, error) { 128 | conn, err := net.ListenIP(network, laddr) 129 | if err != nil { 130 | return nil, err 131 | } 132 | raw, err := conn.SyscallConn() 133 | if err != nil { 134 | conn.Close() 135 | return nil, err 136 | } 137 | _ = raw.Control(func(fd uintptr) { 138 | err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_HDRINCL, 1) 139 | }) 140 | if err != nil { 141 | conn.Close() 142 | return nil, err 143 | } 144 | return conn, nil 145 | } 146 | 147 | // Close closes listening socket. 148 | // Tracer can not be used after Close is called. 149 | func (t *Tracer) Close() { 150 | t.mu.Lock() 151 | defer t.mu.Unlock() 152 | if t.conn != nil { 153 | t.conn.Close() 154 | } 155 | } 156 | 157 | func (t *Tracer) serve(conn *net.IPConn) error { 158 | defer conn.Close() 159 | buf := make([]byte, 1500) 160 | for { 161 | n, from, err := conn.ReadFromIP(buf) 162 | if err != nil { 163 | return err 164 | } 165 | err = t.serveData(from.IP, buf[:n]) 166 | if err != nil { 167 | continue 168 | } 169 | } 170 | } 171 | 172 | func (t *Tracer) serveData(from net.IP, b []byte) error { 173 | if from.To4() == nil { 174 | // TODO: implement ProtocolIPv6ICMP 175 | return errUnsupportedProtocol 176 | } 177 | now := time.Now() 178 | msg, err := icmp.ParseMessage(ProtocolICMP, b) 179 | if err != nil { 180 | return err 181 | } 182 | if msg.Type == ipv4.ICMPTypeEchoReply { 183 | echo := msg.Body.(*icmp.Echo) 184 | return t.serveReply(from, &packet{from, uint16(echo.ID), 1, now}) 185 | } 186 | b = getReplyData(msg) 187 | if len(b) < ipv4.HeaderLen { 188 | return errMessageTooShort 189 | } 190 | switch b[0] >> 4 { 191 | case ipv4.Version: 192 | ip, err := ipv4.ParseHeader(b) 193 | if err != nil { 194 | return err 195 | } 196 | return t.serveReply(ip.Dst, &packet{from, uint16(ip.ID), ip.TTL, now}) 197 | case ipv6.Version: 198 | ip, err := ipv6.ParseHeader(b) 199 | if err != nil { 200 | return err 201 | } 202 | return t.serveReply(ip.Dst, &packet{from, uint16(ip.FlowLabel), ip.HopLimit, now}) 203 | default: 204 | return errUnsupportedProtocol 205 | } 206 | } 207 | 208 | func (t *Tracer) sendRequest(dst net.IP, ttl int) (*packet, error) { 209 | id := uint16(atomic.AddUint32(&t.seq, 1)) 210 | b := newPacket(id, dst, ttl) 211 | req := &packet{dst, id, ttl, time.Now()} 212 | _, err := t.conn.WriteToIP(b, &net.IPAddr{IP: dst}) 213 | if err != nil { 214 | return nil, err 215 | } 216 | return req, nil 217 | } 218 | 219 | func (t *Tracer) addSession(s *Session) { 220 | t.mu.Lock() 221 | defer t.mu.Unlock() 222 | if t.sess == nil { 223 | t.sess = make(map[string][]*Session) 224 | } 225 | t.sess[string(s.ip)] = append(t.sess[string(s.ip)], s) 226 | } 227 | 228 | func (t *Tracer) removeSession(s *Session) { 229 | t.mu.Lock() 230 | defer t.mu.Unlock() 231 | a := t.sess[string(s.ip)] 232 | for i, it := range a { 233 | if it == s { 234 | t.sess[string(s.ip)] = append(a[:i], a[i+1:]...) 235 | return 236 | } 237 | } 238 | } 239 | 240 | func (t *Tracer) serveReply(dst net.IP, res *packet) error { 241 | t.mu.RLock() 242 | defer t.mu.RUnlock() 243 | a := t.sess[string(shortIP(dst))] 244 | for _, s := range a { 245 | s.handle(res) 246 | } 247 | return nil 248 | } 249 | 250 | // Session is a tracer session. 251 | type Session struct { 252 | t *Tracer 253 | ip net.IP 254 | ch chan *Reply 255 | 256 | mu sync.RWMutex 257 | probes []*packet 258 | } 259 | 260 | // NewSession returns new session. 261 | func NewSession(ip net.IP) (*Session, error) { 262 | return DefaultTracer.NewSession(ip) 263 | } 264 | 265 | func newSession(t *Tracer, ip net.IP) *Session { 266 | s := &Session{ 267 | t: t, 268 | ip: ip, 269 | ch: make(chan *Reply, 64), 270 | } 271 | t.addSession(s) 272 | return s 273 | } 274 | 275 | // Ping sends single ICMP packet with specified TTL. 276 | func (s *Session) Ping(ttl int) error { 277 | req, err := s.t.sendRequest(s.ip, ttl+1) 278 | if err != nil { 279 | return err 280 | } 281 | s.mu.Lock() 282 | s.probes = append(s.probes, req) 283 | s.mu.Unlock() 284 | return nil 285 | } 286 | 287 | // Receive returns channel to receive ICMP replies. 288 | func (s *Session) Receive() <-chan *Reply { 289 | return s.ch 290 | } 291 | 292 | // isDone returns true if session does not have unresponsed requests with TTL <= ttl. 293 | func (s *Session) isDone(ttl int) bool { 294 | s.mu.RLock() 295 | defer s.mu.RUnlock() 296 | for _, r := range s.probes { 297 | if r.TTL <= ttl { 298 | return false 299 | } 300 | } 301 | return true 302 | } 303 | 304 | func (s *Session) handle(res *packet) { 305 | now := res.Time 306 | n := 0 307 | var req *packet 308 | s.mu.Lock() 309 | for _, r := range s.probes { 310 | if now.Sub(r.Time) > s.t.Timeout { 311 | continue 312 | } 313 | if r.ID == res.ID { 314 | req = r 315 | continue 316 | } 317 | s.probes[n] = r 318 | n++ 319 | } 320 | s.probes = s.probes[:n] 321 | s.mu.Unlock() 322 | if req == nil { 323 | return 324 | } 325 | hops := req.TTL - res.TTL + 1 326 | if hops < 1 { 327 | hops = 1 328 | } 329 | select { 330 | case s.ch <- &Reply{ 331 | IP: res.IP, 332 | RTT: res.Time.Sub(req.Time), 333 | Hops: hops, 334 | }: 335 | default: 336 | } 337 | } 338 | 339 | // Close closes tracer session. 340 | func (s *Session) Close() { 341 | s.t.removeSession(s) 342 | } 343 | 344 | type packet struct { 345 | IP net.IP 346 | ID uint16 347 | TTL int 348 | Time time.Time 349 | } 350 | 351 | func shortIP(ip net.IP) net.IP { 352 | if v := ip.To4(); v != nil { 353 | return v 354 | } 355 | return ip 356 | } 357 | 358 | func getReplyData(msg *icmp.Message) []byte { 359 | switch b := msg.Body.(type) { 360 | case *icmp.TimeExceeded: 361 | return b.Data 362 | case *icmp.DstUnreach: 363 | return b.Data 364 | case *icmp.ParamProb: 365 | return b.Data 366 | } 367 | return nil 368 | } 369 | 370 | var ( 371 | errMessageTooShort = errors.New("message too short") 372 | errUnsupportedProtocol = errors.New("unsupported protocol") 373 | errNoReplyData = errors.New("no reply data") 374 | ) 375 | 376 | func newPacket(id uint16, dst net.IP, ttl int) []byte { 377 | // TODO: reuse buffers... 378 | msg := icmp.Message{ 379 | Type: ipv4.ICMPTypeEcho, 380 | Body: &icmp.Echo{ 381 | ID: int(id), 382 | Seq: int(id), 383 | }, 384 | } 385 | p, _ := msg.Marshal(nil) 386 | ip := &ipv4.Header{ 387 | Version: ipv4.Version, 388 | Len: ipv4.HeaderLen, 389 | TotalLen: ipv4.HeaderLen + len(p), 390 | TOS: 16, 391 | ID: int(id), 392 | Dst: dst, 393 | Protocol: ProtocolICMP, 394 | TTL: ttl, 395 | } 396 | buf, err := ip.Marshal() 397 | if err != nil { 398 | return nil 399 | } 400 | return append(buf, p...) 401 | } 402 | 403 | // IANA Assigned Internet Protocol Numbers 404 | const ( 405 | ProtocolICMP = 1 406 | ProtocolTCP = 6 407 | ProtocolUDP = 17 408 | ProtocolIPv6ICMP = 58 409 | ) 410 | 411 | // Reply is a reply packet. 412 | type Reply struct { 413 | IP net.IP 414 | RTT time.Duration 415 | Hops int 416 | } 417 | 418 | // Node is a detected network node. 419 | type Node struct { 420 | IP net.IP 421 | RTT []time.Duration 422 | } 423 | 424 | // Hop is a set of detected nodes. 425 | type Hop struct { 426 | Nodes []*Node 427 | Distance int 428 | } 429 | 430 | // Add adds node from r. 431 | func (h *Hop) Add(r *Reply) *Node { 432 | var node *Node 433 | for _, it := range h.Nodes { 434 | if it.IP.Equal(r.IP) { 435 | node = it 436 | break 437 | } 438 | } 439 | if node == nil { 440 | node = &Node{IP: r.IP} 441 | h.Nodes = append(h.Nodes, node) 442 | } 443 | node.RTT = append(node.RTT, r.RTT) 444 | return node 445 | } 446 | 447 | // Trace is a simple traceroute tool using DefaultTracer. 448 | func Trace(ip net.IP) ([]*Hop, error) { 449 | hops := make([]*Hop, 0, DefaultTracer.MaxHops) 450 | touch := func(dist int) *Hop { 451 | for _, h := range hops { 452 | if h.Distance == dist { 453 | return h 454 | } 455 | } 456 | h := &Hop{Distance: dist} 457 | hops = append(hops, h) 458 | return h 459 | } 460 | err := DefaultTracer.Trace(context.Background(), ip, func(r *Reply) { 461 | touch(r.Hops).Add(r) 462 | }) 463 | if err != nil && err != context.DeadlineExceeded { 464 | return nil, err 465 | } 466 | sort.Slice(hops, func(i, j int) bool { 467 | return hops[i].Distance < hops[j].Distance 468 | }) 469 | last := len(hops) - 1 470 | for i := last; i >= 0; i-- { 471 | h := hops[i] 472 | if len(h.Nodes) == 1 && ip.Equal(h.Nodes[0].IP) { 473 | continue 474 | } 475 | if i == last { 476 | break 477 | } 478 | i++ 479 | node := hops[i].Nodes[0] 480 | i++ 481 | for _, it := range hops[i:] { 482 | node.RTT = append(node.RTT, it.Nodes[0].RTT...) 483 | } 484 | hops = hops[:i] 485 | break 486 | } 487 | return hops, nil 488 | } 489 | --------------------------------------------------------------------------------