-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathpinger.go
138 lines (114 loc) · 2.83 KB
/
pinger.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
package main
import (
"log"
"net"
"os"
"sync"
"time"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
)
const protocolICMPIPv4 = 1
type pinger struct {
connection *icmp.PacketConn
quit chan int
dataPoints chan latencyDataPoint
messagesInFlight *pendingEchos
startTime time.Time
targetHost string
}
func newPinger(targetHost string, dataPoints chan latencyDataPoint) *pinger {
return &pinger{
targetHost: targetHost,
dataPoints: dataPoints,
}
}
func (p *pinger) start() {
p.quit = make(chan int)
p.messagesInFlight = newPendingEchoes()
p.startTime = time.Now()
var err error
p.connection, err = icmp.ListenPacket("udp4", "0.0.0.0")
if err != nil {
log.Fatal(err)
}
go p.consumer()
go p.producer(p.targetHost, 10*time.Millisecond)
}
func (p *pinger) stop() {
close(p.quit)
p.connection.Close()
}
func (p *pinger) producer(destinationIP string, interval time.Duration) {
body := &icmp.Echo{
ID: os.Getpid() & 0xffff,
Seq: 0,
Data: []byte("Now is the time for all good homo sapiens to come to the aid of their species"),
}
msg := icmp.Message{
Type: ipv4.ICMPTypeEcho,
Code: 0,
Body: body,
}
for {
wb, err := msg.Marshal(nil)
if err != nil {
log.Fatal(err)
}
p.messagesInFlight.start(body.Seq)
if _, err := p.connection.WriteTo(wb, &net.UDPAddr{IP: net.ParseIP(destinationIP)}); err != nil {
log.Printf("error sending echo request: %v", err)
}
body.Seq++
time.Sleep(interval)
}
}
func (p *pinger) consumer() {
rb := make([]byte, 1500)
for {
n, peer, err := p.connection.ReadFrom(rb)
if err != nil {
log.Fatal(err)
}
candidateReceiptTime := time.Now()
rm, err := icmp.ParseMessage(protocolICMPIPv4, rb[:n])
if err != nil {
log.Fatal(err)
}
switch rm.Type {
case ipv4.ICMPTypeEchoReply:
echoReply := rm.Body.(*icmp.Echo)
echoRequestSentTime, ok := p.messagesInFlight.resolve(echoReply.Seq)
if !ok {
log.Printf("unexpected message #%d, sent at %v", echoReply.Seq, echoRequestSentTime)
continue
}
timeOffset := echoRequestSentTime.Sub(p.startTime).Seconds()
latency := float64(candidateReceiptTime.Sub(echoRequestSentTime).Nanoseconds()) / 1e6
p.dataPoints <- latencyDataPoint{timeOffset: timeOffset, latency: latency}
default:
log.Printf("unexpected message from %v: got %+v, want echo reply", peer, rm)
}
}
}
type pendingEchos struct {
mux sync.Mutex
times map[int]time.Time
}
func newPendingEchoes() *pendingEchos {
return &pendingEchos{
times: make(map[int]time.Time),
}
}
func (mt *pendingEchos) start(sequenceNumber int) {
mt.mux.Lock()
mt.times[sequenceNumber] = time.Now()
mt.mux.Unlock()
}
func (mt *pendingEchos) resolve(sequenceNumber int) (time.Time, bool) {
mt.mux.Lock()
defer mt.mux.Unlock()
time, ok := mt.times[sequenceNumber]
delete(mt.times, sequenceNumber)
return time, ok
}