/
gping.py
executable file
·262 lines (201 loc) · 7.96 KB
/
gping.py
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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
#!/usr/bin/python
__author__ = "Marcin Kozlowski <marcinguy@gmail.com>"
"""
This part is a fork of the python-ping project that makes
things work with gevent.
"""
import os
import struct
import sys
import time
import random
import gevent
from gevent import socket
from gevent.pool import Pool
from gevent.event import Event
import argparse
import pprint
from tqdm import tqdm
# From /usr/include/linux/icmp.h; your milage may vary.
ICMP_ECHO_REQUEST = 8 # Seems to be the same on Solaris.
def checksum(source_string):
"""
I'm not too confident that this is right but testing seems
to suggest that it gives the same answers as in_cksum in ping.c
"""
sum = 0
count_to = (len(source_string) / 2) * 2
for count in xrange(0, count_to, 2):
this = ord(source_string[count + 1]) * 256 + ord(source_string[count])
sum = sum + this
sum = sum & 0xffffffff # Necessary?
if count_to < len(source_string):
sum = sum + ord(source_string[len(source_string) - 1])
sum = sum & 0xffffffff # Necessary?
sum = (sum >> 16) + (sum & 0xffff)
sum = sum + (sum >> 16)
answer = ~sum
answer = answer & 0xffff
# Swap bytes. Bugger me if I know why.
answer = answer >> 8 | (answer << 8 & 0xff00)
return answer
def test_callback(ping):
if(ping['success']):
# PING SUCCESS
print str(ping['dest_addr'])+","+str(ping['success'])+","+ping['desc']
class GPing:
"""
This class, when instantiated will start listening for ICMP responses.
Then call its send method to send pings. Callbacks will be sent ping
details
"""
def __init__(self,timeout=6,max_outstanding=5000):
"""
:timeout - amount of time a ICMP echo request can be outstanding
:max_outstanding - maximum number of outstanding ICMP echo requests without responses (limits traffic)
"""
self.timeout = timeout
self.max_outstanding = max_outstanding
# id we will increment with each ping
self.id = 0
# object to hold and keep track of all of our self.pings
self.pings = {}
# event to file when we want to shut down
self.die_event = Event()
# setup socket
icmp = socket.getprotobyname("icmp")
try:
self.socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, icmp)
except socket.error, (errno, msg):
if errno == 1:
# Operation not permitted
msg = msg + (
" - Note that ICMP messages can only be sent from processes"
" running as root."
)
raise socket.error(msg)
raise # raise the original error
self.receive_glet = gevent.spawn(self.__receive__)
self.processto_glet = gevent.spawn(self.__process_timeouts__)
def die(self):
"""
try to shut everything down gracefully
"""
self.die_event.set()
socket.cancel_wait()
gevent.joinall([self.receive_glet,self.processto_glet])
def join(self):
"""
does a lot of nothing until self.pings is empty
"""
while len(self.pings):
gevent.sleep()
def send(self, dest_addr, desc, callback, psize=64):
"""
Send a ICMP echo request.
:dest_addr - where to send it
:desc - description
:callback - what to call when we get a response
:psize - how much data to send with it
"""
# make sure we dont have too many outstanding requests
while len(self.pings) >= self.max_outstanding:
gevent.sleep()
#resolve hostnames
dest_addr = socket.gethostbyname(dest_addr)
# figure out our id
packet_id = self.id
# increment our id, but wrap if we go over the max size for USHORT
self.id = (self.id + 1) % 2 ** 16
# make a spot for this ping in self.pings
self.pings[packet_id] = {'sent':False,'success':False,'error':False,'dest_addr':dest_addr,'desc':desc,'callback':callback}
# Remove header size from packet size
psize = psize - 8
# Header is type (8), code (8), checksum (16), id (16), sequence (16)
my_checksum = 0
# Make a dummy heder with a 0 checksum.
header = struct.pack("bbHHh", ICMP_ECHO_REQUEST, 0, my_checksum, packet_id, 1)
bytes = struct.calcsize("d")
data = (psize - bytes) * "Q"
data = struct.pack("d", time.time()) + data
# Calculate the checksum on the data and the dummy header.
my_checksum = checksum(header + data)
# Now that we have the right checksum, we put that in. It's just easier
# to make up a new header than to stuff it into the dummy.
header = struct.pack(
"bbHHh", ICMP_ECHO_REQUEST, 0, socket.htons(my_checksum), packet_id, 1
)
packet = header + data
# note the send_time for checking for timeouts
self.pings[packet_id]['send_time'] = time.time()
# send the packet
self.socket.sendto(packet, (dest_addr, 1)) # Don't know about the 1
#mark the packet as sent
self.pings[packet_id]['sent'] = True
def __process_timeouts__(self):
"""
check to see if any of our pings have timed out
"""
while not self.die_event.is_set():
for i in self.pings:
# PING FAIL
if self.pings[i]['sent'] and time.time() - self.pings[i]['send_time'] > self.timeout:
self.pings[i]['error'] = True
self.pings[i]['callback'](self.pings[i])
print(str(self.pings[i]['dest_addr']) + ",False," + self.pings[i]['desc'])
del(self.pings[i])
break
gevent.sleep()
def __receive__(self):
"""
receive response packets
"""
while not self.die_event.is_set():
# wait till we can recv
try:
socket.wait_read(self.socket.fileno())
except socket.error, (errno,msg):
if errno == socket.EBADF:
print "interrupting wait_read"
return
# reraise original exceptions
print "re-throwing socket exception on wait_read()"
raise
time_received = time.time()
received_packet, addr = self.socket.recvfrom(1024)
icmpHeader = received_packet[20:28]
type, code, checksum, packet_id, sequence = struct.unpack(
"bbHHh", icmpHeader
)
if packet_id in self.pings:
bytes_received = struct.calcsize("d")
time_sent = struct.unpack("d", received_packet[28:28 + bytes_received])[0]
self.pings[packet_id]['delay'] = time_received - time_sent
# i'd call that a success
self.pings[packet_id]['success'] = True
# call our callback if we've got one
self.pings[packet_id]['callback'](self.pings[packet_id])
# delete the ping
del(self.pings[packet_id])
if __name__ == '__main__':
if os.geteuid() != 0:
exit("You need to have root privileges to run this script.\nPlease try again, this time using 'sudo'. Exiting.")
gp = GPing()
#for domain in tqdm(data):
for target in sys.stdin.readlines():
if target[0] == '#':
continue
hostname, desc = target.rstrip().split(',', 1)
if hostname[0] == '-':
print hostname[1:]+","+"Skipped"+","+desc
continue
if hostname[0] == '+':
print hostname[1:]+","+"Fault"+","+desc
continue
if hostname[0] == '!':
print hostname[1:]+","+"Kyndryl"+","+desc
continue
hostname, desc = target.rstrip().split(',', 1)
#sys.stderr.write(hostname + "\n")
gp.send(hostname,desc, test_callback)
gp.join()