/
zping.go
171 lines (148 loc) · 3.94 KB
/
zping.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
// Copyright (c) 2011, Daniel Garcia
// All rights reserved.
//
// This program is licensed under the BSD License.
// See LICENSE file for details.
package main
import (
"log"
"bytes"
"fmt"
"net/http"
"os"
"net"
"time"
)
const ICMP_ECHO_REQUEST = 8
const ICMP_ECHO_REPLY = 0
// returns a suitable 'ping request' packet, with id & seq and a
// payload length of pktlen
func makePingRequest(id, seq, pktlen int, filler []byte) []byte {
p := make([]byte, pktlen)
copy(p[8:], bytes.Repeat(filler, (pktlen-8)/len(filler)+1))
p[0] = ICMP_ECHO_REQUEST // type
p[1] = 0 // code
p[2] = 0 // cksum
p[3] = 0 // cksum
p[4] = uint8(id >> 8) // id
p[5] = uint8(id & 0xff) // id
p[6] = uint8(seq >> 8) // sequence
p[7] = uint8(seq & 0xff) // sequence
// calculate icmp checksum
cklen := len(p)
s := uint32(0)
for i := 0; i < (cklen - 1); i += 2 {
s += uint32(p[i+1])<<8 | uint32(p[i])
}
if cklen&1 == 1 {
s += uint32(p[cklen-1])
}
s = (s >> 16) + (s & 0xffff)
s = s + (s >> 16)
// place checksum back in header; using ^= avoids the
// assumption the checksum bytes are zero
p[2] ^= uint8(^s & 0xff)
p[3] ^= uint8(^s >> 8)
return p
}
type PingResponse struct {
dsthost string
rtt int64
}
type PingRequest struct {
dsthost string
responseChannel chan PingResponse
}
func parsePingReply(p []byte) (id, seq int) {
id = int(p[4])<<8 | int(p[5])
seq = int(p[6])<<8 | int(p[7])
return
}
func PingPoller(service chan PingRequest) {
srchost := ""
for {
r := <-service
log.Printf("Received request for %s", r.dsthost)
var (
laddr *net.IPAddr
err error
)
raddr, err := net.ResolveIPAddr("ip", r.dsthost)
if err != nil {
log.Println(`net.ResolveIPAddr("%v") = %v, %v`, r.dsthost, raddr, err)
reply := PingResponse{r.dsthost, -1}
r.responseChannel <- reply
continue
}
c, err := net.ListenIP("ip4:icmp", laddr)
if err != nil {
log.Println(`net.ListenIP("ip4:icmp", %v) = %v, %v`, srchost, c, err)
reply := PingResponse{r.dsthost, -1}
r.responseChannel <- reply
continue
}
sendid := os.Getpid() & 0xffff
const sendseq = 61455
const pingpktlen = 128
sendpkt := makePingRequest(sendid, sendseq, pingpktlen, []byte("Go Go Gadget Ping!!!"))
start := time.Now()
n, err := c.WriteToIP(sendpkt, raddr)
if err != nil || n != pingpktlen {
log.Println(`net.WriteToIP(..., %v) = %v, %v`, raddr, n, err)
reply := PingResponse{r.dsthost, -1}
r.responseChannel <- reply
continue
}
c.SetReadDeadline(time.Now().Add(time.Second * 2))
resp := make([]byte, 1024)
for {
n, from, err := c.ReadFrom(resp)
if err != nil {
log.Println(`ReadFrom(...) = %v, %v, %v`, n, from, err)
reply := PingResponse{r.dsthost, -1}
r.responseChannel <- reply
break
}
if resp[0] != ICMP_ECHO_REPLY {
continue
}
rcvid, rcvseq := parsePingReply(resp)
end := time.Now()
if rcvid != sendid || rcvseq != sendseq {
log.Println(`Ping reply saw id,seq=0x%x,0x%x (expected 0x%x, 0x%x)`, rcvid, rcvseq, sendid, sendseq)
reply := PingResponse{r.dsthost, -1}
r.responseChannel <- reply
break
}
log.Println("response took %d nanoseconds.", end.Sub(start))
reply := PingResponse{r.dsthost, int64(end.Sub(start))}
r.responseChannel <- reply
break
}
log.Println("saw no ping return")
reply := PingResponse{r.dsthost, -1}
r.responseChannel <- reply
}
}
func handler(w http.ResponseWriter, r *http.Request) {
responseChan := make(chan PingResponse)
request := PingRequest{r.URL.Path[1:], responseChan}
requestChan <- request
var (
response PingResponse
)
response = <-request.responseChannel
fmt.Fprintln(w, "response took %d nanoseconds.", response.rtt)
}
var (
requestChan chan PingRequest
)
func main() {
if os.Getuid() != 0 {
log.Fatal("test disabled; must be root")
}
requestChan = make(chan PingRequest)
go PingPoller(requestChan)
http.HandleFunc("/", handler)
http.ListenAndServe(":8081", nil)
}