-
Notifications
You must be signed in to change notification settings - Fork 6
/
ping.go
116 lines (103 loc) · 2.49 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
// Based on https://github.com/google/seesaw/blob/master/healthcheck/ping.go
// only for ipv4.
package main
import (
"fmt"
"net"
"os"
"time"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
"inet.af/netaddr"
)
var nextPingCheckerID = os.Getpid() & 0xffff
type pingChecker struct {
IP netaddr.IP
ID int
Seqnum int
Timeout Duration
}
type checker interface {
Check() error
TargetIP() string
}
func newPingChecker(device, address string, timeout Duration) (*pingChecker, error) {
ip, err := netaddr.ParseIP(address)
if err != nil {
return nil, fmt.Errorf("No valid ip for %s", address)
}
id := nextPingCheckerID
nextPingCheckerID++
return &pingChecker{
IP: ip,
ID: id,
Timeout: timeout,
}, nil
}
func (pc *pingChecker) Check() error {
seq := pc.Seqnum
pc.Seqnum++
echo, err := newICMPv4EchoRequest(pc.ID, seq, []byte("Healthcheck"))
if err != nil {
return fmt.Errorf("Cannot construct icmp echo: %v", err)
}
return exchangeICMPEcho(pc.IP, pc.Timeout.Duration, echo)
}
// return a string representation of the checker's target ip
func (pc *pingChecker) TargetIP() string {
return pc.IP.String()
}
func newICMPv4EchoRequest(id, seqnum int, data []byte) ([]byte, error) {
wm := icmp.Message{
Type: ipv4.ICMPTypeEcho, Code: 0,
Body: &icmp.Echo{
ID: id,
Seq: seqnum,
Data: data,
},
}
return wm.Marshal(nil)
}
func exchangeICMPEcho(ip netaddr.IP, timeout time.Duration, echo []byte) error {
c, err := net.ListenPacket("ip4:icmp", "")
if err != nil {
return err
}
defer c.Close()
_, err = c.WriteTo(echo, ip.IPAddr())
if err != nil {
return err
}
c.SetDeadline(time.Now().Add(timeout))
reply := make([]byte, 256)
for {
n, addr, err := c.ReadFrom(reply)
if err != nil {
return err
}
rip := netaddr.MustParseIP(addr.String())
if ip.Compare(rip) != 0 {
continue
}
// 1 == ipv4 ICMP proto number
// https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml
rm, err := icmp.ParseMessage(1, reply[:n])
if err != nil {
return fmt.Errorf("Cannot parse icmp response: %v", err)
}
if rm.Type != ipv4.ICMPTypeEchoReply {
continue
}
em, err := icmp.ParseMessage(1, echo)
if err != nil {
return fmt.Errorf("Cannot parse echo request for veryfication: %v", err)
}
if rm.Body.(*icmp.Echo).ID != em.Body.(*icmp.Echo).ID || rm.Body.(*icmp.Echo).Seq != em.Body.(*icmp.Echo).Seq {
continue
}
// if we reach that point all checks for receiving our echo
// reply have passed
break
}
return nil
}