-
Notifications
You must be signed in to change notification settings - Fork 81
/
ping.go
141 lines (132 loc) · 3.6 KB
/
ping.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package netceptor
import (
"context"
"fmt"
"strings"
"time"
)
// NetcForPing should include all methods of Netceptor needed by the Ping function.
type NetcForPing interface {
ListenPacket(service string) (PacketConner, error)
NewAddr(target string, service string) Addr
NodeID() string
Context() context.Context
}
// Ping calls SendPing to sends a single test packet and waits for a reply or error.
func (s *Netceptor) Ping(ctx context.Context, target string, hopsToLive byte) (time.Duration, string, error) {
return SendPing(ctx, s, target, hopsToLive)
}
// SendPing creates Ping by sending a single test packet and waits for a replay or error.
func SendPing(ctx context.Context, s NetcForPing, target string, hopsToLive byte) (time.Duration, string, error) {
pc, err := s.ListenPacket("")
if err != nil {
return 0, "", err
}
ctxPing, ctxCancel := context.WithCancel(ctx)
defer func() {
ctxCancel()
_ = pc.Close()
}()
pc.SetHopsToLive(hopsToLive)
doneChan := make(chan struct{})
unrCh := pc.SubscribeUnreachable(doneChan)
defer close(doneChan)
type errorResult struct {
err error
fromNode string
}
errorChan := make(chan errorResult)
go func() {
for msg := range unrCh {
errorChan <- errorResult{
err: fmt.Errorf(msg.Problem),
fromNode: msg.ReceivedFromNode,
}
}
}()
startTime := time.Now()
replyChan := make(chan string)
go func() {
buf := make([]byte, 8)
_, addr, err := pc.ReadFrom(buf)
fromNode := ""
if addr != nil {
fromNode = addr.String()
fromNode = strings.TrimSuffix(fromNode, ":ping")
}
if err == nil {
select {
case replyChan <- fromNode:
case <-ctxPing.Done():
case <-s.Context().Done():
}
} else {
select {
case errorChan <- errorResult{
err: err,
fromNode: fromNode,
}:
case <-ctx.Done():
case <-s.Context().Done():
}
}
}()
_, err = pc.WriteTo([]byte{}, s.NewAddr(target, "ping"))
if err != nil {
return time.Since(startTime), s.NodeID(), err
}
select {
case errRes := <-errorChan:
return time.Since(startTime), errRes.fromNode, errRes.err
case remote := <-replyChan:
return time.Since(startTime), remote, nil
case <-time.After(10 * time.Second):
return time.Since(startTime), "", fmt.Errorf("timeout")
case <-ctxPing.Done():
return time.Since(startTime), "", fmt.Errorf("user cancelled")
case <-s.Context().Done():
return time.Since(startTime), "", fmt.Errorf("netceptor shutdown")
}
}
type NetcForTraceroute interface {
MaxForwardingHops() byte
Ping(ctx context.Context, target string, hopsToLive byte) (time.Duration, string, error)
Context() context.Context
}
// TracerouteResult is the result of one hop of a traceroute.
type TracerouteResult struct {
From string
Time time.Duration
Err error
}
func (s *Netceptor) Traceroute(ctx context.Context, target string) <-chan *TracerouteResult {
return CreateTraceroute(ctx, s, target)
}
// CreateTraceroute returns a channel which will receive a series of hops between this node and the target.
func CreateTraceroute(ctx context.Context, s NetcForTraceroute, target string) <-chan *TracerouteResult {
results := make(chan *TracerouteResult)
go func() {
defer close(results)
for i := 0; i <= int(s.MaxForwardingHops()); i++ {
pingTime, pingRemote, err := s.Ping(ctx, target, byte(i))
res := &TracerouteResult{
From: pingRemote,
Time: pingTime,
}
if err != nil && err.Error() != ProblemExpiredInTransit {
res.Err = err
}
select {
case results <- res:
case <-ctx.Done():
return
case <-s.Context().Done():
return
}
if res.Err != nil || err == nil {
return
}
}
}()
return results
}