-
Notifications
You must be signed in to change notification settings - Fork 0
/
listener.go
178 lines (154 loc) · 4.7 KB
/
listener.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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
package goping
import (
"fmt"
"log"
"net"
"os"
"sync/atomic"
"time"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
)
var chping chan *Request
var checho chan rawIcmp
var identifier int = (os.Getpid() & 0xffff)
var seq int32 //The Next Sequence to be used
//Requests a Synchronous Ping (Internally channels are used)
func Ping(p *Request) (*Pong, error) {
chpong := make(chan *Pong) //Channel that will be used to receive the Pong
defer close(chpong)
PingOnChan(p, chpong) //Calls the PingChan function and waits for the answer
pong := <-chpong //Waits for the pong object
return pong, pong.Err //Returns the pong object and the error if any
}
func PingOnChan(req *Request, c chan *Pong) {
//Assign the return channel to the Request
req.pong = c
//Got the sequence number for the ping
req.seq = int(atomic.AddInt32(&seq, 1))
//Validate the Ip on Request
ip, err := net.ResolveIPAddr("ip4", req.To)
if err != nil {
go func() {
req.pong <- &Pong{Request: req, Err: fmt.Errorf("Could not resolve Ip address for: %v", req.To)}
}()
return
}
//Assign the resolved IP address to the request
req.ip = ip
//Send request to process
go func() {
chping <- req
}()
}
type Request struct {
To string //The Ip or FQDN of the host to ping
Timeout int //Timeout to receive a pong (answer for the ping)
pong chan *Pong //The channel to receive the Pong Object
seq int //The Ping sequence
ip *net.IPAddr
rawicmp chan *rawIcmp //Channel to receive the raw ICMP
}
type Pong struct {
Request *Request //The ping request
Rtt float64 //The time elapsed between the ping sent over the wire and the arrived pong response
Err error
Packet *icmp.Echo
Id int //The id of this ping. The PID will be used
Seq int //Sequence of the ping created byt this api
done chan bool // Indicates we receive a response, either a Pong or a Timeout
}
type rawIcmp struct {
when time.Time
size int
peer net.Addr
bytes []byte
message *icmp.Echo //The message after being parsed
err error
}
//Parse the message. Generate error if Id is differente from PID
func (r *rawIcmp) parseMessage() error {
if message, err := icmp.ParseMessage(1, r.bytes[:r.size]); err != nil {
return err
} else {
r.message = message.Body.(*icmp.Echo)
}
return nil
}
func init() {
chping := make(chan *Request)
//Opens the raw socket using the package golang/x/icmp from google
c, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
if err != nil {
log.Fatal("Could not open raw socket ip4:icmp: %v", err)
}
//Starts an infinite loop to read from raw socket icmp
go func() {
//Opens the raw socket using the package golang/x/icmp from google
c, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
if err != nil {
log.Fatal("Could not open raw socket ip4:icmp: %v", err)
}
for {
//Reads an ICMP Message from the Socket.
ri := rawIcmp{bytes: make([]byte, 1500)}
if ri.size, ri.peer, ri.err = c.ReadFrom(ri.bytes); ri.err != nil {
log.Fatal("Could not read from socket: %v", ri.err)
}
//Tags the time when the message arrived. This will be used to calc RTT
ri.when = time.Now()
//Sends the Message to the checho channel
go func(ri rawIcmp) {
checho <- ri
}(ri)
}
}()
go func() {
requests := make(map[int]chan<- *rawIcmp)
for {
select {
case req := <-chping:
wm := icmp.Message{
Type: ipv4.ICMPTypeEcho, Code: 0,
Body: &icmp.Echo{
ID: identifier, Seq: req.seq,
Data: []byte("HELLO-R-U-THERE####All ALL OTHER STUFF!"),
},
}
if wb, err := wm.Marshal(nil); err != nil {
go func() {
req.pong <- &Pong{Request: req, Err: fmt.Errorf("Could not marshal icmp packet: [%v] %v", err, req)}
}()
} else {
if _, err := c.WriteTo(wb, req.ip); err != nil {
go func() {
req.pong <- &Pong{Request: req, Err: fmt.Errorf("Could not write to socket: [%v] %v", err, req)}
}()
}
}
start := time.Now()
requests[req.seq] = req.rawicmp
go func(req *Request, start time.Time) {
select {
case raw := <-req.rawicmp: //Received icmp response
req.pong <- &Pong{
Request: req,
Rtt: float64(time.Since(start)-time.Since(raw.when)) * float64(time.Second/time.Millisecond),
Packet: raw.message,
}
delete(requests, req.seq)
case <-time.After(time.Second * time.Duration(req.Timeout)): //Icmp Response Timed out
req.pong <- &Pong{Request: req, Err: fmt.Errorf("Timed Out", err, req)}
delete(requests, req.seq)
}
}(req, start)
case raw := <-checho:
if raw.message.ID == identifier {
if requests[raw.message.Seq] != nil {
requests[raw.message.Seq] <- &raw
}
}
}
}
}()
}