]>
git.ipfire.org Git - collecty.git/blob - collecty/ping.py
7982b6a4a64acb0e92a11dede8caaf9bc642fcbf
3 from __future__
import division
15 ICMP_TYPE_ECHO_REPLY
= 0
16 ICMP_TYPE_ECHO_REQUEST
= 8
21 class PingError(Exception):
25 class PingResolveError(PingError
):
26 msg
= "Could not resolve hostname"
30 def __init__(self
, destination
, timeout
=1000, packet_size
=56):
31 self
.destination
= self
._resolve
(destination
)
32 self
.timeout
= timeout
33 self
.packet_size
= packet_size
35 self
.id = os
.getpid() & 0xffff # XXX ? Is this a good idea?
39 # Number of sent packets.
42 # Save the delay of all responses.
45 def run(self
, count
=None, deadline
=None):
51 if count
and self
.seq_number
>= count
:
54 if deadline
and self
.total_time
>= deadline
:
61 time
.sleep((MAX_SLEEP
- delay
) / 1000)
66 # Open a socket for ICMP communication.
67 s
= socket
.socket(socket
.AF_INET
, socket
.SOCK_RAW
, socket
.getprotobyname("icmp"))
70 send_time
= self
.send_icmp_echo_request(s
)
72 # Increase number of sent packets (even if it could not be sent).
75 # If the packet could not be sent, we may stop here.
80 receive_time
, packet_size
, ip
, ip_header
, icmp_header
= self
.receive_icmp_echo_reply(s
)
87 # If a packet has been received...
89 delay
= (receive_time
- send_time
) * 1000
90 self
.times
.append(delay
)
94 def send_icmp_echo_request(self
, s
):
95 # Header is type (8), code (8), checksum (16), id (16), sequence (16)
98 # Create a header with checksum == 0.
99 header
= struct
.pack("!BBHHH", ICMP_TYPE_ECHO_REQUEST
, 0,
100 checksum
, self
.id, self
.seq_number
)
102 # Get some bytes for padding.
103 padding
= os
.urandom(self
.packet_size
)
105 # Calculate the checksum for header + padding data.
106 checksum
= self
._calculate
_checksum
(header
+ padding
)
108 # Rebuild the header with the new checksum.
109 header
= struct
.pack("!BBHHH", ICMP_TYPE_ECHO_REQUEST
, 0,
110 checksum
, self
.id, self
.seq_number
)
113 packet
= header
+ padding
115 # Save the time when the packet has been sent.
116 send_time
= time
.time()
120 s
.sendto(packet
, (self
.destination
, 0))
121 except socket
.error
, (errno
, msg
):
122 if errno
== 1: # Operation not permitted
123 # The packet could not be sent, probably because of
124 # wrong firewall settings.
129 def receive_icmp_echo_reply(self
, s
):
130 timeout
= self
.timeout
/ 1000.0
132 # Wait until the reply packet arrived or until we hit timeout.
134 select_start
= time
.time()
136 inputready
, outputready
, exceptready
= select
.select([s
], [], [], timeout
)
137 select_duration
= (time
.time() - select_start
)
139 if inputready
== []: # Timeout
140 return None, 0, 0, 0, 0
142 # Save the time when the packet has been received.
143 receive_time
= time
.time()
145 # Read the packet from the socket.
146 packet_data
, address
= s
.recvfrom(ICMP_MAX_RECV
)
148 # Parse the ICMP header.
149 icmp_header
= self
._header
2dict
(
150 ["type", "code", "checksum", "packet_id", "seq_number"],
151 "!BBHHH", packet_data
[20:28]
154 # This is the reply to our packet if the ID matches.
155 if icmp_header
["packet_id"] == self
.id:
156 # Parse the IP header.
157 ip_header
= self
._header
2dict
(
158 ["version", "type", "length", "id", "flags",
159 "ttl", "protocol", "checksum", "src_ip", "dst_ip"],
160 "!BBHHHBBHII", packet_data
[:20]
163 packet_size
= len(packet_data
) - 28
164 ip
= socket
.inet_ntoa(struct
.pack("!I", ip_header
["src_ip"]))
166 return receive_time
, packet_size
, ip
, ip_header
, icmp_header
168 # Check if the timeout has already been hit.
169 timeout
= timeout
- select_duration
171 return None, 0, 0, 0, 0
173 def _header2dict(self
, names
, struct_format
, data
):
175 Unpack tghe raw received IP and ICMP header informations to a dict
177 unpacked_data
= struct
.unpack(struct_format
, data
)
178 return dict(zip(names
, unpacked_data
))
180 def _calculate_checksum(self
, source_string
):
181 if len(source_string
) % 2:
182 source_string
+= "\x00"
184 converted
= array
.array("H", source_string
)
185 if sys
.byteorder
== "big":
190 # Truncate val to 32 bits (a variance from ping.c, which uses signed
191 # integers, but overflow is unlinkely in ping).
194 # Add high 16 bits to low 16 bits.
195 val
= (val
>> 16) + (val
& 0xffff)
197 # Add carry from above (if any).
200 # Invert and truncate to 16 bits.
201 answer
= ~val
& 0xffff
203 return socket
.htons(answer
)
205 def _resolve(self
, host
):
209 if self
._is
_valid
_ipv
4_address
(host
):
213 return socket
.gethostbyname(host
)
214 except PingResolvError
:
215 raise PingResolveError
217 def _is_valid_ipv4_address(self
, addr
):
219 Check addr to be a valid IPv4 address.
221 parts
= addr
.split(".")
223 if not len(parts
) == 4:
238 def receive_count(self
):
240 The number of received packets.
242 return len(self
.times
)
245 def total_time(self
):
247 The total time of all roundtrips.
250 return sum(self
.times
)
257 The smallest roundtrip time.
260 return min(self
.times
)
267 The biggest roundtrip time.
270 return max(self
.times
)
277 Calculate the average response time.
280 return self
.total_time
/ self
.receive_count
281 except ZeroDivisionError:
287 Calculate the variance of all roundtrips.
289 if self
.avg_time
is None:
295 var
+= (t
- self
.avg_time
) ** 2
297 var
/= self
.receive_count
303 Standard deviation of all roundtrips.
305 return math
.sqrt(self
.variance
)
310 Outputs the percentage of dropped packets.
312 dropped
= self
.send_count
- self
.receive_count
314 return dropped
/ self
.send_count
317 if __name__
== "__main__":
318 p
= Ping("ping.ipfire.org")
321 print "Min/Avg/Max/Stddev: %.2f/%.2f/%.2f/%.2f" % \
322 (p
.min_time
, p
.avg_time
, p
.max_time
, p
.stddev
)
323 print "Sent/Recv/Loss: %d/%d/%.2f" % (p
.send_count
, p
.receive_count
, p
.loss
)