├── .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 | 
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 |
--------------------------------------------------------------------------------