Skip to content

Commit

Permalink
Issue romana#25 fixed, Mult_Ping still broken
Browse files Browse the repository at this point in the history
  • Loading branch information
eis-joshua committed Apr 9, 2020
1 parent 687e64a commit a575027
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 14 deletions.
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,13 @@ Here is an example of how to use MultiPing in your own code:
responses, no_responses = mp.receive(1)

The `receive()` function returns a tuple containing a results dictionary
(addresses and response times) as well as a list of addresses that did not
(addresses with response times and retry) as well as a list of addresses that did not
respond in time. The results may be processed like this:

...

for addr, rtt in responses.items():
print "%s responded in %f seconds" % (addr, rtt)
for addr, result in responses.items():
print "%s responded in %f seconds (retry=%d)" % (addr, result['time'], result['retry'])

if no_responses:
print "These addresses did not respond: %s" % ", ".join(no_responses)
Expand Down Expand Up @@ -102,3 +102,8 @@ surpressed if the `silent_lookup_errors` parameter flag is set. Either as named
parameter for the `multi_ping` function or when a `MultiPing` object is
created.

To avoid burst issues with packet loss on some networks, the `delay` parameter
can be used with the `multi_ping` function or when a `MultiPing` object is
created. This delay in seconds will be applied between every ICMP request.
For milliseconds delay simply use floating number, e.g.: `0.001` for 1 ms.

46 changes: 35 additions & 11 deletions multiping/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ class MultiPingSocketError(socket.gaierror):

class MultiPing(object):

def __init__(self, dest_addrs, sock=None, ignore_lookup_errors=False):
def __init__(self, dest_addrs, sock=None, ignore_lookup_errors=False,
delay=0):
"""
Initialize a new multi ping object. This takes the configuration
consisting of the list of destination addresses and an optional socket
Expand All @@ -95,6 +96,7 @@ def __init__(self, dest_addrs, sock=None, ignore_lookup_errors=False):
"65535 addresses at the same time.")

self._ignore_lookup_errors = ignore_lookup_errors
self._delay = delay # delay between each ICMP request

# Get the IP addresses for every specified target: We allow
# specification of the ping targets by name, so a name lookup needs to
Expand Down Expand Up @@ -145,6 +147,7 @@ def __init__(self, dest_addrs, sock=None, ignore_lookup_errors=False):
self._unprocessed_targets.append(d)

self._id_to_addr = {}
self._addr_retry = {}
self._remaining_ids = None
self._last_used_id = None
self._time_stamp_size = struct.calcsize("d")
Expand Down Expand Up @@ -242,7 +245,7 @@ def _send_ping(self, dest_addr, payload):
# - ICMP code = 0 (unsigned byte)
# - checksum = 0 (unsigned short)
# - packet id (unsigned short)
# - sequence = 0 (unsigned short) This doesn't have to be 0.
# - sequence = pid (unsigned short)
dummy_header = bytearray(
struct.pack(_ICMP_HDR_PACK_FORMAT,
icmp_echo_request, 0, 0,
Expand Down Expand Up @@ -292,6 +295,7 @@ def send(self):
# Collect all the addresses for which we have not seen responses yet.
if not self._receive_has_been_called:
all_addrs = self._dest_addrs
self._addr_retry = {addr: -1 for addr in all_addrs}
else:
all_addrs = [a for (i, a) in list(self._id_to_addr.items())
if i in self._remaining_ids]
Expand All @@ -303,8 +307,13 @@ def send(self):
# need to trim it down.
self._last_used_id = int(time.time()) & 0xffff

# Reset the _id_to_addr, we are now retrying to send new request with
# new ID. Reply of a request that have been retried will be ignored.
self._id_to_addr = {}

# Send ICMPecho to all addresses...
for addr in all_addrs:
self._addr_retry[addr] += 1
# Make a unique ID, wrapping around at 65535.
self._last_used_id = (self._last_used_id + 1) & 0xffff
# Remember the address for each ID so we can produce meaningful
Expand All @@ -314,6 +323,14 @@ def send(self):
# of the current time stamp. This is returned to us in the
# response and allows us to calculate the 'ping time'.
self._send_ping(addr, payload=struct.pack("d", time.time()))
# Some system/network doesn't support the bombarding of ICMP
# request and lead to a lot of packet loss and retry, therefore
# introcude a small delay between each request.
if self._delay > 0:
time.sleep(self._delay)

# Keep track of the current request IDs to be used in the receive
self._remaining_ids = list(self._id_to_addr.keys())

def _read_all_from_socket(self, timeout):
"""
Expand All @@ -337,9 +354,9 @@ def _read_all_from_socket(self, timeout):
try:
self._sock.settimeout(timeout)
while True:
p = self._sock.recv(64)
p, src_addr = self._sock.recvfrom(128)
# Store the packet and the current time
pkts.append((bytearray(p), time.time()))
pkts.append((src_addr, bytearray(p), time.time()))
# Continue the loop to receive any additional packets that
# may have arrived at this point. Changing the socket to
# non-blocking (by setting the timeout to 0), so that we'll
Expand All @@ -366,8 +383,8 @@ def _read_all_from_socket(self, timeout):
try:
self._sock6.settimeout(timeout)
while True:
p = self._sock6.recv(128)
pkts.append((bytearray(p), time.time()))
p, src_addr = self._sock6.recvfrom(128)
pkts.append((src_addr, bytearray(p), time.time()))
self._sock6.settimeout(0)
except socket.timeout:
pass
Expand Down Expand Up @@ -415,7 +432,7 @@ def receive(self, timeout):
start_time = time.time()
pkts = self._read_all_from_socket(remaining_time)

for pkt, resp_receive_time in pkts:
for src_addr, pkt, resp_receive_time in pkts:
# Extract the ICMP ID of the response

try:
Expand All @@ -438,7 +455,8 @@ def receive(self, timeout):
payload = pkt[_ICMP_PAYLOAD_OFFSET:]

if pkt_ident == self.ident and \
pkt_id in self._remaining_ids:
pkt_id in self._remaining_ids and \
src_addr[0] == self._id_to_addr[pkt_id]:
# The sending timestamp was encoded in the echo request
# body and is now returned to us in the response. Note
# that network byte order doesn't matter here, since we
Expand All @@ -447,7 +465,8 @@ def receive(self, timeout):
req_sent_time = struct.unpack(
"d", payload[:self._time_stamp_size])[0]
results[self._id_to_addr[pkt_id]] = \
resp_receive_time - req_sent_time
{'time': resp_receive_time - req_sent_time,
'retry': self._addr_retry[src_addr[0]]}

self._remaining_ids.remove(pkt_id)
except IndexError:
Expand All @@ -474,7 +493,7 @@ def __del__(self):
self._sock6.close()


def multi_ping(dest_addrs, timeout, retry=0, ignore_lookup_errors=False):
def multi_ping(dest_addrs, timeout, retry=0, ignore_lookup_errors=False, delay=0):
"""
Combine send and receive measurement into single function.
Expand All @@ -492,11 +511,15 @@ def multi_ping(dest_addrs, timeout, retry=0, ignore_lookup_errors=False):
names or looking up their address information will silently be ignored.
Those targets simply appear in the 'no_results' return list.
The 'delay' parameter can be used to introduced a small delay between
each requests.
"""
retry = int(retry)
if retry < 0:
retry = 0


timeout = float(timeout)
if timeout < 0.1:
raise MultiPingError("Timeout < 0.1 seconds not allowed")
Expand All @@ -505,7 +528,8 @@ def multi_ping(dest_addrs, timeout, retry=0, ignore_lookup_errors=False):
if retry_timeout < 0.1:
raise MultiPingError("Time between ping retries < 0.1 seconds")

mp = MultiPing(dest_addrs, ignore_lookup_errors=ignore_lookup_errors)
mp = MultiPing(dest_addrs, ignore_lookup_errors=ignore_lookup_errors,
delay=delay)

results = {}
retry_count = 0
Expand Down

0 comments on commit a575027

Please sign in to comment.