From 37a50fc87c78c4165a4938d5c2f6bb65eb60d9b9 Mon Sep 17 00:00:00 2001 From: jack Date: Thu, 11 May 2017 15:36:54 +0200 Subject: [PATCH 01/40] We cannot connect() using RAW_SOCKET, let's use a writer; Signed-off-by: jack --- aioping/__init__.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/aioping/__init__.py b/aioping/__init__.py index 359cc28..40765e7 100644 --- a/aioping/__init__.py +++ b/aioping/__init__.py @@ -84,6 +84,7 @@ import socket import struct import time +import functools if sys.platform == "win32": @@ -171,6 +172,11 @@ async def send_one_ping(my_socket, dest_addr, id_, timeout): :param timeout: :return: """ + + def sendto_ready(packet, socket): + my_socket.sendto(packet, (dest_addr, 1)) + asyncio.get_event_loop().remove_writer(my_socket) + try: resolver = aiodns.DNSResolver(timeout=timeout, tries=1) dest_addr = await resolver.gethostbyname(dest_addr, socket.AF_INET) @@ -198,9 +204,8 @@ async def send_one_ping(my_socket, dest_addr, id_, timeout): ) packet = header + data - loop = asyncio.get_event_loop() - await loop.sock_connect(my_socket, (dest_addr, 1)) - await loop.sock_sendall(my_socket, packet) + callback = functools.partial(sendto_ready, packet=packet, socket=socket) + asyncio.get_event_loop().add_writer(my_socket, callback) async def ping(dest_addr, timeout=10): From f995b14cdf416808308d873c5c78e6ee4a54daff Mon Sep 17 00:00:00 2001 From: jack Date: Thu, 11 May 2017 15:43:02 +0200 Subject: [PATCH 02/40] Typo: icmp type is unsigned Signed-off-by: jack --- aioping/__init__.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/aioping/__init__.py b/aioping/__init__.py index 40765e7..38f168b 100644 --- a/aioping/__init__.py +++ b/aioping/__init__.py @@ -94,8 +94,9 @@ # On most other platforms the best timer is time.time() default_timer = time.time -# From /usr/include/linux/icmp.h; your milage may vary. -ICMP_ECHO_REQUEST = 8 # Seems to be the same on Solaris. +# ICMP types, see rfc792 for v4, rfc4443 for v6 +ICMP_ECHO_REQUEST = 8 +ICMP6_ECHO_REQUEST = 128 def checksum(buffer): @@ -147,7 +148,7 @@ async def receive_one_ping(my_socket, id_, timeout): icmp_header = rec_packet[20:28] type, code, checksum, packet_id, sequence = struct.unpack( - "bbHHh", icmp_header + "BbHHh", icmp_header ) # Filters out the echo request itself. @@ -189,7 +190,7 @@ def sendto_ready(packet, socket): my_checksum = 0 # Make a dummy header with a 0 checksum. - header = struct.pack("bbHHh", ICMP_ECHO_REQUEST, 0, my_checksum, id_, 1) + header = struct.pack("BbHHh", ICMP_ECHO_REQUEST, 0, my_checksum, id_, 1) bytes_in_double = struct.calcsize("d") data = (192 - bytes_in_double) * "Q" data = struct.pack("d", default_timer()) + data.encode("ascii") @@ -200,7 +201,7 @@ def sendto_ready(packet, socket): # 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), id_, 1 + "BbHHh", ICMP_ECHO_REQUEST, 0, socket.htons(my_checksum), id_, 1 ) packet = header + data From 864d961b9367fd20e09a57a08f40a610666aae3a Mon Sep 17 00:00:00 2001 From: jack Date: Thu, 11 May 2017 16:33:32 +0200 Subject: [PATCH 03/40] Support ipv6 Signed-off-by: jack --- aioping/__init__.py | 59 +++++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/aioping/__init__.py b/aioping/__init__.py index 38f168b..03ffe4b 100644 --- a/aioping/__init__.py +++ b/aioping/__init__.py @@ -78,7 +78,6 @@ import asyncio import async_timeout -import aiodns import os import sys import socket @@ -97,6 +96,8 @@ # ICMP types, see rfc792 for v4, rfc4443 for v6 ICMP_ECHO_REQUEST = 8 ICMP6_ECHO_REQUEST = 128 +ICMP_ECHO_REPLY = 0 +ICMP6_ECHO_REPLY = 129 def checksum(buffer): @@ -145,18 +146,24 @@ async def receive_one_ping(my_socket, id_, timeout): with async_timeout.timeout(timeout): rec_packet = await loop.sock_recv(my_socket, 1024) time_received = default_timer() - icmp_header = rec_packet[20:28] + + if my_socket.family == socket.AddressFamily.AF_INET: + offset = 20 + else: + offset = 0 + + icmp_header = rec_packet[offset:offset + 8] type, code, checksum, packet_id, sequence = struct.unpack( - "BbHHh", icmp_header + "bbHHh", icmp_header ) - # Filters out the echo request itself. - # This can be tested by pinging 127.0.0.1 - # You'll see your own request - if type != 8 and packet_id == id_: - bytes_in_double = struct.calcsize("d") - time_sent = struct.unpack("d", rec_packet[28:28 + bytes_in_double])[0] + if type != ICMP_ECHO_REPLY and type != ICMP6_ECHO_REPLY: + next + + if packet_id == id_: + data = rec_packet[offset + 8:offset + 8 + struct.calcsize("d")] + time_sent = struct.unpack("d", data)[0] return time_received - time_sent @@ -164,7 +171,7 @@ async def receive_one_ping(my_socket, id_, timeout): raise TimeoutError("Ping timeout") -async def send_one_ping(my_socket, dest_addr, id_, timeout): +async def send_one_ping(my_socket, dest_addr, id_, timeout, family): """ Send one ping to the given >dest_addr<. :param my_socket: @@ -175,22 +182,17 @@ async def send_one_ping(my_socket, dest_addr, id_, timeout): """ def sendto_ready(packet, socket): - my_socket.sendto(packet, (dest_addr, 1)) + my_socket.sendto(packet, dest_addr) asyncio.get_event_loop().remove_writer(my_socket) - try: - resolver = aiodns.DNSResolver(timeout=timeout, tries=1) - dest_addr = await resolver.gethostbyname(dest_addr, socket.AF_INET) - dest_addr = dest_addr.addresses[0] - - except aiodns.error.DNSError: - raise ValueError("Unable to resolve host") + icmp_type = ICMP_ECHO_REQUEST if family == socket.AddressFamily.AF_INET\ + else ICMP6_ECHO_REQUEST # Header is type (8), code (8), checksum (16), id (16), sequence (16) my_checksum = 0 # Make a dummy header with a 0 checksum. - header = struct.pack("BbHHh", ICMP_ECHO_REQUEST, 0, my_checksum, id_, 1) + header = struct.pack("BbHHh", icmp_type, 0, my_checksum, id_, 1) bytes_in_double = struct.calcsize("d") data = (192 - bytes_in_double) * "Q" data = struct.pack("d", default_timer()) + data.encode("ascii") @@ -201,7 +203,7 @@ def sendto_ready(packet, socket): # 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), id_, 1 + "BbHHh", icmp_type, 0, socket.htons(my_checksum), id_, 1 ) packet = header + data @@ -215,11 +217,20 @@ async def ping(dest_addr, timeout=10): :param dest_addr: :param timeout: """ - icmp = socket.getprotobyname("icmp") + + loop = asyncio.get_event_loop() + info = await loop.getaddrinfo(dest_addr, 0) + family = info[2][0] + addr = info[2][4] + + if family == socket.AddressFamily.AF_INET: + icmp = socket.getprotobyname("icmp") + else: + icmp = socket.getprotobyname("ipv6-icmp") try: - my_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, icmp) - my_socket.setblocking(0) + my_socket = socket.socket(family, socket.SOCK_RAW, icmp) + my_socket.setblocking(False) except OSError as e: msg = e.strerror @@ -237,7 +248,7 @@ async def ping(dest_addr, timeout=10): my_id = os.getpid() & 0xFFFF - await send_one_ping(my_socket, dest_addr, my_id, timeout) + await send_one_ping(my_socket, addr, my_id, timeout, family) delay = await receive_one_ping(my_socket, my_id, timeout) my_socket.close() From 4865c851e3bd9ddd20e86d6a0a7debb857c69746 Mon Sep 17 00:00:00 2001 From: jack Date: Thu, 11 May 2017 17:00:56 +0200 Subject: [PATCH 04/40] Handle timeout in verbose_ping Signed-off-by: jack --- aioping/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/aioping/__init__.py b/aioping/__init__.py index 03ffe4b..7b4280e 100644 --- a/aioping/__init__.py +++ b/aioping/__init__.py @@ -270,8 +270,11 @@ async def verbose_ping(dest_addr, timeout=2, count=3): print("%s failed: %s" % (dest_addr, str(e))) break - delay *= 1000 - print("%s get ping in %0.4fms" % (dest_addr, delay)) + if delay is None: + print('%s timed out after %ss' % (dest_addr, timeout)) + else: + delay *= 1000 + print("%s get ping in %0.4fms" % (dest_addr, delay)) print() From 7567fc70b89071194bd2e6d4dbde05bc52b3b69a Mon Sep 17 00:00:00 2001 From: jack Date: Mon, 15 May 2017 09:19:50 +0200 Subject: [PATCH 05/40] getprotobyname only once, that will reduce the # of open files Signed-off-by: jack --- aioping/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/aioping/__init__.py b/aioping/__init__.py index 7b4280e..1b4cdc8 100644 --- a/aioping/__init__.py +++ b/aioping/__init__.py @@ -99,6 +99,9 @@ ICMP_ECHO_REPLY = 0 ICMP6_ECHO_REPLY = 129 +proto_icmp = socket.getprotobyname("icmp") +proto_icmp6 = socket.getprotobyname("ipv6-icmp") + def checksum(buffer): """ @@ -224,9 +227,9 @@ async def ping(dest_addr, timeout=10): addr = info[2][4] if family == socket.AddressFamily.AF_INET: - icmp = socket.getprotobyname("icmp") + icmp = proto_icmp else: - icmp = socket.getprotobyname("ipv6-icmp") + icmp = proto_icmp6 try: my_socket = socket.socket(family, socket.SOCK_RAW, icmp) From 929e1121e3f8dc0ea4165efecbd85459c6e62372 Mon Sep 17 00:00:00 2001 From: jack Date: Mon, 15 May 2017 09:22:08 +0200 Subject: [PATCH 06/40] When using raw socket, all received packet will be read on all sockets. Loop & filter until we find ours (or timeout) Signed-off-by: jack --- aioping/__init__.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/aioping/__init__.py b/aioping/__init__.py index 1b4cdc8..9a122d2 100644 --- a/aioping/__init__.py +++ b/aioping/__init__.py @@ -147,28 +147,29 @@ async def receive_one_ping(my_socket, id_, timeout): try: with async_timeout.timeout(timeout): - rec_packet = await loop.sock_recv(my_socket, 1024) - time_received = default_timer() + while True: + rec_packet = await loop.sock_recv(my_socket, 1024) + time_received = default_timer() - if my_socket.family == socket.AddressFamily.AF_INET: - offset = 20 - else: - offset = 0 + if my_socket.family == socket.AddressFamily.AF_INET: + offset = 20 + else: + offset = 0 - icmp_header = rec_packet[offset:offset + 8] + icmp_header = rec_packet[offset:offset + 8] - type, code, checksum, packet_id, sequence = struct.unpack( - "bbHHh", icmp_header - ) + type, code, checksum, packet_id, sequence = struct.unpack( + "bbHHh", icmp_header + ) - if type != ICMP_ECHO_REPLY and type != ICMP6_ECHO_REPLY: - next + if type != ICMP_ECHO_REPLY and type != ICMP6_ECHO_REPLY: + next - if packet_id == id_: - data = rec_packet[offset + 8:offset + 8 + struct.calcsize("d")] - time_sent = struct.unpack("d", data)[0] + if packet_id == id_: + data = rec_packet[offset + 8:offset + 8 + struct.calcsize("d")] + time_sent = struct.unpack("d", data)[0] - return time_received - time_sent + return time_received - time_sent except asyncio.TimeoutError: raise TimeoutError("Ping timeout") From 9375d2a3b76c6363ae5740906b2c268cbe5e4eec Mon Sep 17 00:00:00 2001 From: jack Date: Tue, 16 May 2017 14:32:20 +0200 Subject: [PATCH 07/40] Send packets synchronously (I dunno how to do it properly) Signed-off-by: jack --- aioping/__init__.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/aioping/__init__.py b/aioping/__init__.py index 9a122d2..462d399 100644 --- a/aioping/__init__.py +++ b/aioping/__init__.py @@ -78,12 +78,11 @@ import asyncio import async_timeout -import os import sys import socket import struct import time -import functools +import uuid if sys.platform == "win32": @@ -185,10 +184,6 @@ async def send_one_ping(my_socket, dest_addr, id_, timeout, family): :return: """ - def sendto_ready(packet, socket): - my_socket.sendto(packet, dest_addr) - asyncio.get_event_loop().remove_writer(my_socket) - icmp_type = ICMP_ECHO_REQUEST if family == socket.AddressFamily.AF_INET\ else ICMP6_ECHO_REQUEST @@ -211,8 +206,7 @@ def sendto_ready(packet, socket): ) packet = header + data - callback = functools.partial(sendto_ready, packet=packet, socket=socket) - asyncio.get_event_loop().add_writer(my_socket, callback) + my_socket.sendto(packet, dest_addr) async def ping(dest_addr, timeout=10): @@ -250,7 +244,7 @@ async def ping(dest_addr, timeout=10): raise - my_id = os.getpid() & 0xFFFF + my_id = uuid.uuid4().int & 0xFFFF await send_one_ping(my_socket, addr, my_id, timeout, family) delay = await receive_one_ping(my_socket, my_id, timeout) From 1ca0443f1ac1b63903404c40af98f1283c6b0834 Mon Sep 17 00:00:00 2001 From: jack Date: Wed, 9 Aug 2017 18:39:14 +0200 Subject: [PATCH 08/40] fix: use sendto asynchronously Signed-off-by: jack --- aioping/__init__.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/aioping/__init__.py b/aioping/__init__.py index 462d399..6cc1ea3 100644 --- a/aioping/__init__.py +++ b/aioping/__init__.py @@ -82,6 +82,7 @@ import socket import struct import time +import functools import uuid @@ -174,6 +175,12 @@ async def receive_one_ping(my_socket, id_, timeout): raise TimeoutError("Ping timeout") +def sendto_ready(packet, socket, future, dest): + socket.sendto(packet, dest) + asyncio.get_event_loop().remove_writer(socket) + future.set_result(None) + + async def send_one_ping(my_socket, dest_addr, id_, timeout, family): """ Send one ping to the given >dest_addr<. @@ -206,7 +213,10 @@ async def send_one_ping(my_socket, dest_addr, id_, timeout, family): ) packet = header + data - my_socket.sendto(packet, dest_addr) + future = asyncio.get_event_loop().create_future() + callback = functools.partial(sendto_ready, packet=packet, socket=my_socket, dest=dest_addr, future=future) + asyncio.get_event_loop().add_writer(my_socket, callback) + await future async def ping(dest_addr, timeout=10): From 8fd1be5d98d4d21a9a7a762543582ca17a9c3075 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 9 Nov 2017 17:50:07 +0100 Subject: [PATCH 09/40] s/next/continue/: this ain't perl Signed-off-by: Jack --- aioping/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aioping/__init__.py b/aioping/__init__.py index 6cc1ea3..edeffd5 100644 --- a/aioping/__init__.py +++ b/aioping/__init__.py @@ -163,7 +163,7 @@ async def receive_one_ping(my_socket, id_, timeout): ) if type != ICMP_ECHO_REPLY and type != ICMP6_ECHO_REPLY: - next + continue if packet_id == id_: data = rec_packet[offset + 8:offset + 8 + struct.calcsize("d")] From f48ec95c4ae8131ea8d604556c6030582bf9f873 Mon Sep 17 00:00:00 2001 From: Albert Santoni Date: Mon, 16 Apr 2018 11:09:42 -0400 Subject: [PATCH 10/40] Fix issue #5: Does not work on Windows * If multiple IPs are resolved from a hostname, randomly choose one of those IPs instead of hardcoding it to use the third one. --- aioping/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/aioping/__init__.py b/aioping/__init__.py index edeffd5..93eef49 100644 --- a/aioping/__init__.py +++ b/aioping/__init__.py @@ -84,6 +84,7 @@ import time import functools import uuid +import random if sys.platform == "win32": @@ -228,8 +229,10 @@ async def ping(dest_addr, timeout=10): loop = asyncio.get_event_loop() info = await loop.getaddrinfo(dest_addr, 0) - family = info[2][0] - addr = info[2][4] + # Choose one of the IPs resolved by DNS + resolved = random.choice(info) + family = resolved[0] + addr = resolved[4] if family == socket.AddressFamily.AF_INET: icmp = proto_icmp From 8c239df92a3c0b0a5a33aa69be27d233e1b3bb0a Mon Sep 17 00:00:00 2001 From: Edd Date: Tue, 8 May 2018 15:17:54 +0300 Subject: [PATCH 11/40] Added install_requires --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 758afd8..6d1fbad 100644 --- a/setup.py +++ b/setup.py @@ -5,6 +5,7 @@ name="aioping", packages=["aioping"], version="0.1.0", + install_requires=["async_timeout", "aiodns"], description="Asyncio ping implementation", author="Anton Belousov", author_email="anton@stellarbit.com", From 61cbfc0f0c91771599a9dbed4169662507ad010f Mon Sep 17 00:00:00 2001 From: Anton Belousov Date: Fri, 15 Jun 2018 19:15:13 +0300 Subject: [PATCH 12/40] Update README.rst --- README.rst | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 85a4813..b46dec2 100644 --- a/README.rst +++ b/README.rst @@ -4,11 +4,15 @@ aioping is a fast asyncio implementation of ICMP (ping) protocol. Installation ------------ -aioping requires Python 3.5 and is not yet available on PyPI. -Use pip to install it:: +aioping requires Python 3.5. - $ pip install git+https://github.com/stellarbit/aioping +Use pip to install it from the PyPI:: + + $ pip install aioping +Or use the latest version from the master (if you are brave enough):: + + $ pip install git+https://github.com/stellarbit/aioping Using aioping ------------ @@ -62,6 +66,13 @@ Credits - Rewrite by Anton Belousov / Stellarbit LLC http://github.com/stellarbit/aioping + +- Generous contributions from GitHub users: + + - https://github.com/JackSlateur + - https://github.com/harriv + - https://github.com/asantoni + - https://github.com/eddebc License From 66ae42257ca42c5158931df6da56d62ae19dece1 Mon Sep 17 00:00:00 2001 From: Anton Belousov Date: Fri, 15 Jun 2018 19:18:34 +0300 Subject: [PATCH 13/40] setup.py version updated. --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 6d1fbad..cca0dbf 100644 --- a/setup.py +++ b/setup.py @@ -4,13 +4,13 @@ setup( name="aioping", packages=["aioping"], - version="0.1.0", + version="0.2.0", install_requires=["async_timeout", "aiodns"], description="Asyncio ping implementation", author="Anton Belousov", author_email="anton@stellarbit.com", url="https://github.com/stellarbit/aioping", - download_url="https://github.com/stellarbit/aioping/tarball/0.1.0", + download_url="https://github.com/stellarbit/aioping/tarball/0.2.0", keywords=["network", "icmp", "ping", "asyncio"], classifiers=[ "Development Status :: 4 - Beta", From d56694ce7af73a5048680f68eb919fde957d9416 Mon Sep 17 00:00:00 2001 From: Anton Belousov Date: Fri, 15 Jun 2018 19:24:05 +0300 Subject: [PATCH 14/40] setup.py fixed for updated setuptools --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index cca0dbf..b5ad000 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,5 @@ # coding: utf8 -from distutils.core import setup +from setuptools import setup setup( name="aioping", From eae01588cd66c97c3d89df48b81c6b4086df8ee0 Mon Sep 17 00:00:00 2001 From: Christopher Dickey Date: Fri, 20 Jul 2018 17:45:54 +0000 Subject: [PATCH 15/40] Moved from print to standard logging --- aioping/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/aioping/__init__.py b/aioping/__init__.py index 93eef49..b135e54 100644 --- a/aioping/__init__.py +++ b/aioping/__init__.py @@ -76,6 +76,7 @@ $Author: $ """ +import logging import asyncio import async_timeout import sys @@ -278,14 +279,14 @@ async def verbose_ping(dest_addr, timeout=2, count=3): try: delay = await ping(dest_addr, timeout) except Exception as e: - print("%s failed: %s" % (dest_addr, str(e))) + logger.exception("%s failed: %s" % (dest_addr, str(e))) break if delay is None: - print('%s timed out after %ss' % (dest_addr, timeout)) + logger.error('%s timed out after %ss' % (dest_addr, timeout)) else: delay *= 1000 - print("%s get ping in %0.4fms" % (dest_addr, delay)) + logger.info("%s get ping in %0.4fms" % (dest_addr, delay)) print() From 4c00805b4c927f78c3f7710a02403329901e4fb2 Mon Sep 17 00:00:00 2001 From: hergla Date: Mon, 20 Aug 2018 14:43:56 +0200 Subject: [PATCH 16/40] Update __init__.py --- aioping/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aioping/__init__.py b/aioping/__init__.py index 93eef49..83d0d42 100644 --- a/aioping/__init__.py +++ b/aioping/__init__.py @@ -173,6 +173,7 @@ async def receive_one_ping(my_socket, id_, timeout): return time_received - time_sent except asyncio.TimeoutError: + my_socket.close() raise TimeoutError("Ping timeout") From 73a48af392fd25fd1d9126e09d1d56bf93c316d5 Mon Sep 17 00:00:00 2001 From: Roman Andriadi Date: Tue, 23 Apr 2019 14:03:51 +0100 Subject: [PATCH 17/40] Handle exceptions in the sending callback --- aioping/__init__.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/aioping/__init__.py b/aioping/__init__.py index 93eef49..04caf05 100644 --- a/aioping/__init__.py +++ b/aioping/__init__.py @@ -177,9 +177,17 @@ async def receive_one_ping(my_socket, id_, timeout): def sendto_ready(packet, socket, future, dest): - socket.sendto(packet, dest) - asyncio.get_event_loop().remove_writer(socket) - future.set_result(None) + try: + socket.sendto(packet, dest) + except (BlockingIOError, InterruptedError): + return # The callback will be retried + except Exception as exc: + asyncio.get_event_loop().remove_writer(socket) + future.set_exception(exc) + else: + asyncio.get_event_loop().remove_writer(socket) + future.set_result(None) + async def send_one_ping(my_socket, dest_addr, id_, timeout, family): From b129aa9ceaf9145e7ceb2609ee2dc5f58210a0de Mon Sep 17 00:00:00 2001 From: Anton Belousov Date: Sat, 4 Jul 2020 16:23:00 +0300 Subject: [PATCH 18/40] PR-8 logging --- README.rst | 3 ++- aioping/__init__.py | 16 +++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/README.rst b/README.rst index b46dec2..b17485c 100644 --- a/README.rst +++ b/README.rst @@ -19,7 +19,7 @@ Using aioping There are 2 ways to use the library. -First one is interactive, which prints results to the stdout. +First one is interactive, which sends results to standard Python logger. Please make sure you are running this code under root, as only root is allowed to send ICMP packets: @@ -73,6 +73,7 @@ Credits - https://github.com/harriv - https://github.com/asantoni - https://github.com/eddebc + - https://github.com/wise0wl License diff --git a/aioping/__init__.py b/aioping/__init__.py index b135e54..a28f3f4 100644 --- a/aioping/__init__.py +++ b/aioping/__init__.py @@ -87,6 +87,8 @@ import uuid import random +logger = logging.getLogger("aioping") + if sys.platform == "win32": # On Windows, the best timer is time.clock() @@ -276,19 +278,19 @@ async def verbose_ping(dest_addr, timeout=2, count=3): :param count: """ for i in range(count): + delay = None + try: delay = await ping(dest_addr, timeout) + except TimeoutError as e: + logger.error("%s timed out after %ss" % (dest_addr, timeout)) except Exception as e: - logger.exception("%s failed: %s" % (dest_addr, str(e))) + logger.error("%s failed: %s" % (dest_addr, str(e))) break - if delay is None: - logger.error('%s timed out after %ss' % (dest_addr, timeout)) - else: + if delay is not None: delay *= 1000 - logger.info("%s get ping in %0.4fms" % (dest_addr, delay)) - - print() + logger.warning("%s get ping in %0.4fms" % (dest_addr, delay)) if __name__ == "__main__": From 0c4cf0ba679b9c84a15c00b6f033d7a0b46382b1 Mon Sep 17 00:00:00 2001 From: Anton Belousov Date: Sat, 4 Jul 2020 17:20:58 +0300 Subject: [PATCH 19/40] Testing code --- aioping/__init__.py | 14 +------------- aioping/tests/__init__.py | 0 aioping/tests/test_aioping.py | 28 ++++++++++++++++++++++++++++ 3 files changed, 29 insertions(+), 13 deletions(-) create mode 100644 aioping/tests/__init__.py create mode 100644 aioping/tests/test_aioping.py diff --git a/aioping/__init__.py b/aioping/__init__.py index a28f3f4..6ad0296 100644 --- a/aioping/__init__.py +++ b/aioping/__init__.py @@ -220,6 +220,7 @@ async def send_one_ping(my_socket, dest_addr, id_, timeout, family): future = asyncio.get_event_loop().create_future() callback = functools.partial(sendto_ready, packet=packet, socket=my_socket, dest=dest_addr, future=future) asyncio.get_event_loop().add_writer(my_socket, callback) + await future @@ -291,16 +292,3 @@ async def verbose_ping(dest_addr, timeout=2, count=3): if delay is not None: delay *= 1000 logger.warning("%s get ping in %0.4fms" % (dest_addr, delay)) - - -if __name__ == "__main__": - loop = asyncio.get_event_loop() - - tasks = [ - asyncio.ensure_future(verbose_ping("heise.de")), - asyncio.ensure_future(verbose_ping("google.com")), - asyncio.ensure_future(verbose_ping("a-test-url-taht-is-not-available.com")), - asyncio.ensure_future(verbose_ping("192.168.1.111")) - ] - - loop.run_until_complete(asyncio.gather(*tasks)) diff --git a/aioping/tests/__init__.py b/aioping/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/aioping/tests/test_aioping.py b/aioping/tests/test_aioping.py new file mode 100644 index 0000000..353578e --- /dev/null +++ b/aioping/tests/test_aioping.py @@ -0,0 +1,28 @@ +from unittest import TestCase +import asyncio +from aioping import verbose_ping, ping + + +class TestAioping(TestCase): + def test_verbose_ping(self): + loop = asyncio.get_event_loop() + + tasks = [ + asyncio.ensure_future(verbose_ping("heise.de")), + asyncio.ensure_future(verbose_ping("google.com")), + asyncio.ensure_future(verbose_ping("a-test-url-taht-is-not-available.com")), + asyncio.ensure_future(verbose_ping("192.168.1.111")) + ] + + loop.run_until_complete(asyncio.gather(*tasks)) + + async def _do_ping(self, host): + try: + delay = await ping(host) * 1000 + print("Ping response from %s in %s ms" % (host, delay)) + except TimeoutError: + print("Timed out") + + def test_ping(self): + loop = asyncio.get_event_loop() + loop.run_until_complete(self._do_ping("192.168.0.255")) From 6f5f03b4f524ce533fc3e6cbc2321cf04838c272 Mon Sep 17 00:00:00 2001 From: Anton Belousov Date: Sat, 4 Jul 2020 18:29:42 +0300 Subject: [PATCH 20/40] Debug output for host resolution --- README.rst | 1 + aioping/__init__.py | 5 +++++ aioping/tests/test_aioping.py | 5 +++++ 3 files changed, 11 insertions(+) diff --git a/README.rst b/README.rst index b17485c..614c3a6 100644 --- a/README.rst +++ b/README.rst @@ -74,6 +74,7 @@ Credits - https://github.com/asantoni - https://github.com/eddebc - https://github.com/wise0wl + - https://github.com/nARN License diff --git a/aioping/__init__.py b/aioping/__init__.py index c8e7216..36f7986 100644 --- a/aioping/__init__.py +++ b/aioping/__init__.py @@ -241,11 +241,16 @@ async def ping(dest_addr, timeout=10): loop = asyncio.get_event_loop() info = await loop.getaddrinfo(dest_addr, 0) + + logger.debug("getaddrinfo result=%s", info) + # Choose one of the IPs resolved by DNS resolved = random.choice(info) family = resolved[0] addr = resolved[4] + logger.debug("resolved addr=%s", addr) + if family == socket.AddressFamily.AF_INET: icmp = proto_icmp else: diff --git a/aioping/tests/test_aioping.py b/aioping/tests/test_aioping.py index 353578e..6cd5d7d 100644 --- a/aioping/tests/test_aioping.py +++ b/aioping/tests/test_aioping.py @@ -1,9 +1,14 @@ from unittest import TestCase import asyncio from aioping import verbose_ping, ping +import logging class TestAioping(TestCase): + def __init__(self, methodName): + super().__init__(methodName) + logging.basicConfig(level=logging.DEBUG) + def test_verbose_ping(self): loop = asyncio.get_event_loop() From e84ae673314742b6e64e7b04c2e79cb6e6b09a04 Mon Sep 17 00:00:00 2001 From: Anton Belousov Date: Sat, 4 Jul 2020 18:34:16 +0300 Subject: [PATCH 21/40] IPv4 fix --- aioping/__init__.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/aioping/__init__.py b/aioping/__init__.py index 36f7986..1cf8851 100644 --- a/aioping/__init__.py +++ b/aioping/__init__.py @@ -242,14 +242,20 @@ async def ping(dest_addr, timeout=10): loop = asyncio.get_event_loop() info = await loop.getaddrinfo(dest_addr, 0) - logger.debug("getaddrinfo result=%s", info) - - # Choose one of the IPs resolved by DNS - resolved = random.choice(info) + logger.debug("%s getaddrinfo result=%s", dest_addr, info) + + # Choose one of the v4 IPs resolved by DNS + resolved = list(filter( + lambda i: i[0] == socket.AddressFamily.AF_INET and i[1] == socket.SocketKind.SOCK_DGRAM, + info + )) + + resolved = random.choice(resolved) + family = resolved[0] addr = resolved[4] - logger.debug("resolved addr=%s", addr) + logger.debug("%s resolved addr=%s", dest_addr, addr) if family == socket.AddressFamily.AF_INET: icmp = proto_icmp From 245b58f21891bfeb52baece46f8ca5f245e4bb64 Mon Sep 17 00:00:00 2001 From: Anton Belousov Date: Sat, 4 Jul 2020 18:36:37 +0300 Subject: [PATCH 22/40] Tests fix --- aioping/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aioping/__init__.py b/aioping/__init__.py index 1cf8851..426d5ce 100644 --- a/aioping/__init__.py +++ b/aioping/__init__.py @@ -310,4 +310,4 @@ async def verbose_ping(dest_addr, timeout=2, count=3): if delay is not None: delay *= 1000 - logger.warning("%s get ping in %0.4fms" % (dest_addr, delay)) + logger.info("%s get ping in %0.4fms" % (dest_addr, delay)) From defc647c8429fc025a0fb4c6e691ae90744de501 Mon Sep 17 00:00:00 2001 From: Anton Belousov Date: Sat, 4 Jul 2020 18:48:27 +0300 Subject: [PATCH 23/40] Contributors list updated. --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index 614c3a6..55280e3 100644 --- a/README.rst +++ b/README.rst @@ -75,6 +75,7 @@ Credits - https://github.com/eddebc - https://github.com/wise0wl - https://github.com/nARN + - https://github.com/hergla License From 29ec0941811f688cd9eb13e5535e14b6f186ee22 Mon Sep 17 00:00:00 2001 From: Anton Belousov Date: Sat, 4 Jul 2020 19:07:41 +0300 Subject: [PATCH 24/40] Windows fix for Python 3.8 --- README.rst | 1 + aioping/__init__.py | 14 +++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/README.rst b/README.rst index 55280e3..5820273 100644 --- a/README.rst +++ b/README.rst @@ -76,6 +76,7 @@ Credits - https://github.com/wise0wl - https://github.com/nARN - https://github.com/hergla + - https://github.com/hanieljgoertz License diff --git a/aioping/__init__.py b/aioping/__init__.py index 768dd12..6337cc0 100644 --- a/aioping/__init__.py +++ b/aioping/__init__.py @@ -88,14 +88,14 @@ import random logger = logging.getLogger("aioping") +default_timer = time.time - -if sys.platform == "win32": - # On Windows, the best timer is time.clock() - default_timer = time.clock -else: - # On most other platforms the best timer is time.time() - default_timer = time.time +if sys.platform.startswith("win"): + # time.clock is deprecated in Python 3.8+ + if sys.version_info[0] > 3 or (sys.version_info[0] == 3 and sys.version_info[1] >= 8): + asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) + else: + default_timer = time.clock # ICMP types, see rfc792 for v4, rfc4443 for v6 ICMP_ECHO_REQUEST = 8 From 1b886cb7017ca84c9adaf4b5ddf5deee5d1c4c81 Mon Sep 17 00:00:00 2001 From: Anton Belousov Date: Sat, 4 Jul 2020 19:11:16 +0300 Subject: [PATCH 25/40] Release updates. --- setup.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index b5ad000..8fab0c1 100644 --- a/setup.py +++ b/setup.py @@ -4,13 +4,13 @@ setup( name="aioping", packages=["aioping"], - version="0.2.0", + version="0.3.0", install_requires=["async_timeout", "aiodns"], description="Asyncio ping implementation", author="Anton Belousov", author_email="anton@stellarbit.com", url="https://github.com/stellarbit/aioping", - download_url="https://github.com/stellarbit/aioping/tarball/0.2.0", + download_url="https://github.com/stellarbit/aioping/tarball/0.3.0", keywords=["network", "icmp", "ping", "asyncio"], classifiers=[ "Development Status :: 4 - Beta", @@ -18,6 +18,8 @@ "License :: OSI Approved :: GNU General Public License v2 (GPLv2)", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", "Topic :: Software Development :: Libraries :: Python Modules", ] ) From 642e709b001af23bdb446ae51f1cb96b00f36874 Mon Sep 17 00:00:00 2001 From: Anton Belousov Date: Sun, 5 Jul 2020 13:55:15 +0300 Subject: [PATCH 26/40] issue 17 fix attempt --- README.rst | 2 ++ aioping/__init__.py | 17 ++++++++--------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/README.rst b/README.rst index 5820273..538e7ce 100644 --- a/README.rst +++ b/README.rst @@ -27,7 +27,9 @@ root is allowed to send ICMP packets: import asyncio import aioping + import logging + logging.basicConfig(level=logging.INFO) # or logging.DEBUG loop = asyncio.get_event_loop() loop.run_until_complete(aioping.verbose_ping("google.com")) diff --git a/aioping/__init__.py b/aioping/__init__.py index 6337cc0..022cffb 100644 --- a/aioping/__init__.py +++ b/aioping/__init__.py @@ -176,7 +176,10 @@ async def receive_one_ping(my_socket, id_, timeout): return time_received - time_sent except asyncio.TimeoutError: + asyncio.get_event_loop().remove_writer(my_socket) + asyncio.get_event_loop().remove_reader(my_socket) my_socket.close() + raise TimeoutError("Ping timeout") @@ -193,7 +196,6 @@ def sendto_ready(packet, socket, future, dest): future.set_result(None) - async def send_one_ping(my_socket, dest_addr, id_, timeout, family): """ Send one ping to the given >dest_addr<. @@ -203,7 +205,6 @@ async def send_one_ping(my_socket, dest_addr, id_, timeout, family): :param timeout: :return: """ - icmp_type = ICMP_ECHO_REQUEST if family == socket.AddressFamily.AF_INET\ else ICMP6_ECHO_REQUEST @@ -233,11 +234,12 @@ async def send_one_ping(my_socket, dest_addr, id_, timeout, family): await future -async def ping(dest_addr, timeout=10): +async def ping(dest_addr, timeout=10, family=None): """ Returns either the delay (in seconds) or raises an exception. :param dest_addr: :param timeout: + :param family: """ loop = asyncio.get_event_loop() @@ -245,13 +247,10 @@ async def ping(dest_addr, timeout=10): logger.debug("%s getaddrinfo result=%s", dest_addr, info) - # Choose one of the v4 IPs resolved by DNS - resolved = list(filter( - lambda i: i[0] == socket.AddressFamily.AF_INET and i[1] == socket.SocketKind.SOCK_DGRAM, - info - )) + if family is not None: + info = list(filter(lambda i: i[0] == family, info)) - resolved = random.choice(resolved) + resolved = random.choice(info) family = resolved[0] addr = resolved[4] From a8cfecf139bf2cdefdbfeef06860e76069c276e5 Mon Sep 17 00:00:00 2001 From: Anton Belousov Date: Sun, 5 Jul 2020 14:12:44 +0300 Subject: [PATCH 27/40] Tests improved --- aioping/__init__.py | 8 ++++++-- aioping/tests/test_aioping.py | 24 ++++++++++++++++-------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/aioping/__init__.py b/aioping/__init__.py index 022cffb..b3f4e89 100644 --- a/aioping/__init__.py +++ b/aioping/__init__.py @@ -250,6 +250,9 @@ async def ping(dest_addr, timeout=10, family=None): if family is not None: info = list(filter(lambda i: i[0] == family, info)) + if len(info) == 0: + raise socket.gaierror("%s hostname not found for address family %s" % (dest_addr, family)) + resolved = random.choice(info) family = resolved[0] @@ -289,19 +292,20 @@ async def ping(dest_addr, timeout=10, family=None): return delay -async def verbose_ping(dest_addr, timeout=2, count=3): +async def verbose_ping(dest_addr, timeout=2, count=3, family=None): """ Send >count< ping to >dest_addr< with the given >timeout< and display the result. :param dest_addr: :param timeout: :param count: + :param family: """ for i in range(count): delay = None try: - delay = await ping(dest_addr, timeout) + delay = await ping(dest_addr, timeout, family) except TimeoutError as e: logger.error("%s timed out after %ss" % (dest_addr, timeout)) except Exception as e: diff --git a/aioping/tests/test_aioping.py b/aioping/tests/test_aioping.py index 6cd5d7d..f3fb6cc 100644 --- a/aioping/tests/test_aioping.py +++ b/aioping/tests/test_aioping.py @@ -2,21 +2,24 @@ import asyncio from aioping import verbose_ping, ping import logging +import socket class TestAioping(TestCase): def __init__(self, methodName): super().__init__(methodName) - logging.basicConfig(level=logging.DEBUG) + logging.basicConfig(level=logging.INFO) def test_verbose_ping(self): loop = asyncio.get_event_loop() tasks = [ - asyncio.ensure_future(verbose_ping("heise.de")), - asyncio.ensure_future(verbose_ping("google.com")), asyncio.ensure_future(verbose_ping("a-test-url-taht-is-not-available.com")), - asyncio.ensure_future(verbose_ping("192.168.1.111")) + asyncio.ensure_future(verbose_ping("192.168.1.111")), + asyncio.ensure_future(verbose_ping("heise.de", family=socket.AddressFamily.AF_INET)), + asyncio.ensure_future(verbose_ping("google.com", family=socket.AddressFamily.AF_INET)), + asyncio.ensure_future(verbose_ping("heise.de", family=socket.AddressFamily.AF_INET6)), + asyncio.ensure_future(verbose_ping("google.com", family=socket.AddressFamily.AF_INET6)) ] loop.run_until_complete(asyncio.gather(*tasks)) @@ -24,10 +27,15 @@ def test_verbose_ping(self): async def _do_ping(self, host): try: delay = await ping(host) * 1000 - print("Ping response from %s in %s ms" % (host, delay)) + print("%s ping response in %s ms" % (host, delay)) except TimeoutError: - print("Timed out") + print("%s timed out" % host) - def test_ping(self): + def test_many_pings(self): loop = asyncio.get_event_loop() - loop.run_until_complete(self._do_ping("192.168.0.255")) + tasks = [] + + for i in range(255): + tasks.append(asyncio.ensure_future(self._do_ping("192.168.0.%s" % i))) + + loop.run_until_complete(asyncio.gather(*tasks)) From a8b94fe21770eb59765ce0b3a0b090b8390f0e44 Mon Sep 17 00:00:00 2001 From: Crypto-Spartan Date: Sun, 5 Jul 2020 14:32:35 -0400 Subject: [PATCH 28/40] Update __init__.py change timer to time.perf_counter --- aioping/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/aioping/__init__.py b/aioping/__init__.py index 6337cc0..575cb43 100644 --- a/aioping/__init__.py +++ b/aioping/__init__.py @@ -88,14 +88,12 @@ import random logger = logging.getLogger("aioping") -default_timer = time.time +default_timer = time.perf_counter if sys.platform.startswith("win"): # time.clock is deprecated in Python 3.8+ if sys.version_info[0] > 3 or (sys.version_info[0] == 3 and sys.version_info[1] >= 8): asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) - else: - default_timer = time.clock # ICMP types, see rfc792 for v4, rfc4443 for v6 ICMP_ECHO_REQUEST = 8 From ed0b89b6ac47420b3b99b855c24a759f9c28155f Mon Sep 17 00:00:00 2001 From: Crypto-Spartan Date: Sun, 5 Jul 2020 14:32:56 -0400 Subject: [PATCH 29/40] Update __init__.py remove comment --- aioping/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/aioping/__init__.py b/aioping/__init__.py index 575cb43..d34e577 100644 --- a/aioping/__init__.py +++ b/aioping/__init__.py @@ -91,7 +91,6 @@ default_timer = time.perf_counter if sys.platform.startswith("win"): - # time.clock is deprecated in Python 3.8+ if sys.version_info[0] > 3 or (sys.version_info[0] == 3 and sys.version_info[1] >= 8): asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) From 557e02cf4f904c1366b73fcab92623e90b6b3340 Mon Sep 17 00:00:00 2001 From: Anton Belousov Date: Mon, 6 Jul 2020 02:12:20 +0300 Subject: [PATCH 30/40] Tests adjusted, added contributors. --- README.rst | 1 + aioping/tests/test_aioping.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 538e7ce..f9da6f6 100644 --- a/README.rst +++ b/README.rst @@ -79,6 +79,7 @@ Credits - https://github.com/nARN - https://github.com/hergla - https://github.com/hanieljgoertz + - https://github.com/Crypto-Spartan License diff --git a/aioping/tests/test_aioping.py b/aioping/tests/test_aioping.py index f3fb6cc..09ff87f 100644 --- a/aioping/tests/test_aioping.py +++ b/aioping/tests/test_aioping.py @@ -27,7 +27,7 @@ def test_verbose_ping(self): async def _do_ping(self, host): try: delay = await ping(host) * 1000 - print("%s ping response in %s ms" % (host, delay)) + print("%s ping response in %0.4fms" % (host, delay)) except TimeoutError: print("%s timed out" % host) From 0ad3a94a29aff317e8b6fa257d74bb59bd960f13 Mon Sep 17 00:00:00 2001 From: Anton Belousov Date: Mon, 6 Jul 2020 02:15:53 +0300 Subject: [PATCH 31/40] New distribution changes. --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 8fab0c1..cf47625 100644 --- a/setup.py +++ b/setup.py @@ -4,13 +4,13 @@ setup( name="aioping", packages=["aioping"], - version="0.3.0", + version="0.3.1", install_requires=["async_timeout", "aiodns"], description="Asyncio ping implementation", author="Anton Belousov", author_email="anton@stellarbit.com", url="https://github.com/stellarbit/aioping", - download_url="https://github.com/stellarbit/aioping/tarball/0.3.0", + download_url="https://github.com/stellarbit/aioping/tarball/0.3.1", keywords=["network", "icmp", "ping", "asyncio"], classifiers=[ "Development Status :: 4 - Beta", From a65c5fb24b0cc5c52a3d38dcd3df4ef94b781c8a Mon Sep 17 00:00:00 2001 From: Anton Belousov Date: Mon, 6 Jul 2020 02:29:11 +0300 Subject: [PATCH 32/40] Docs updated. --- README.rst | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index f9da6f6..2ffcd37 100644 --- a/README.rst +++ b/README.rst @@ -15,7 +15,7 @@ Or use the latest version from the master (if you are brave enough):: $ pip install git+https://github.com/stellarbit/aioping Using aioping ------------- +------------- There are 2 ways to use the library. @@ -53,6 +53,23 @@ error: loop = asyncio.get_event_loop() loop.run_until_complete(do_ping("google.com")) +Methods +------- + +``ping(dest_addr, timeout=10, family=None)`` + +- ``dest_addr`` - destination address, IPv4, IPv6 or hostname +- ``timeout`` - timeout in seconds (default: ``10``) +- ``family`` - family of resolved address - ``socket.AddressFamily.AF_INET`` for IPv4, ``socket.AddressFamily.AF_INET6`` + for IPv6 or ``None`` if it doesn't matter (default: ``None``) + +``verbose_ping(dest_addr, timeout=2, count=3, family=None)`` + +- ``dest_addr`` - destination address, IPv4, IPv6 or hostname +- ``timeout`` - timeout in seconds (default: ``2``) +- ``count`` - count of packets to send (default: ``3``) +- ``family`` - family of resolved address - ``socket.AddressFamily.AF_INET`` for IPv4, ``socket.AddressFamily.AF_INET6`` + for IPv6 or ``None`` if it doesn't matter (default: ``None``) Credits ------- From f66cbd62eb47e0f6e604ee88fecdc6e4f9a94cb8 Mon Sep 17 00:00:00 2001 From: Crypto-Spartan Date: Sat, 18 Jul 2020 02:05:46 -0400 Subject: [PATCH 33/40] Added description for multiping, changed examples to asyncio.run() instead of loop.run_until_complete() --- README.rst | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/README.rst b/README.rst index 2ffcd37..fd6685a 100644 --- a/README.rst +++ b/README.rst @@ -17,7 +17,7 @@ Or use the latest version from the master (if you are brave enough):: Using aioping ------------- -There are 2 ways to use the library. +There are 3 ways to use the library. First one is interactive, which sends results to standard Python logger. Please make sure you are running this code under root, as only @@ -30,10 +30,9 @@ root is allowed to send ICMP packets: import logging logging.basicConfig(level=logging.INFO) # or logging.DEBUG - loop = asyncio.get_event_loop() - loop.run_until_complete(aioping.verbose_ping("google.com")) + asyncio.run(aioping.verbose_ping("google.com")) -Alternatively, you can call a ping function, which returns a +Secondly, you can call a ping function, which returns a ping delay in milliseconds or throws an exception in case of error: @@ -44,14 +43,31 @@ error: async def do_ping(host): try: - delay = await aioping.ping(host) * 1000 + delay = await aioping.ping(host) print("Ping response in %s ms" % delay) except TimeoutError: print("Timed out") - loop = asyncio.get_event_loop() - loop.run_until_complete(do_ping("google.com")) + asyncio.run(do_ping("google.com")) + +The last way is to call a multiping function, which returns a +list of tuples. The tuples are formatted as (dest_addr, delay) with +delay measured in milliseconds. In the event of a timeout, the tuple +will be returned as (dest_addr, 'TimeoutError'). Lowering the timeout +will result in a faster return. NOTE: This function is limited to 255 +pings at one time due to the limitation of select(). + +.. code:: python + + import asyncio + import aioping + + async def do_multiping(): + results = await aioping.multiping(['8.8.8.8','1.1.1.1','google.com']) + print(results) + + asyncio.run(do_multiping()) Methods ------- @@ -70,6 +86,13 @@ Methods - ``count`` - count of packets to send (default: ``3``) - ``family`` - family of resolved address - ``socket.AddressFamily.AF_INET`` for IPv4, ``socket.AddressFamily.AF_INET6`` for IPv6 or ``None`` if it doesn't matter (default: ``None``) + +``multiping(dest_addr, timeout=5, family=None)`` + +- ``dest_addr`` - destination address, IPv4, IPv6 or hostname +- ``timeout`` - timeout in seconds (default: ``5``) +- ``family`` - family of resolved address - ``socket.AddressFamily.AF_INET`` for IPv4, ``socket.AddressFamily.AF_INET6`` + for IPv6 or ``None`` if it doesn't matter (default: ``None``) Credits ------- From 5110a1c17d80cf68b88851333590421bdd638521 Mon Sep 17 00:00:00 2001 From: Crypto-Spartan Date: Sat, 18 Jul 2020 02:06:59 -0400 Subject: [PATCH 34/40] add leading underscore to internal functions --- aioping/__init__.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/aioping/__init__.py b/aioping/__init__.py index d594d62..68f6140 100644 --- a/aioping/__init__.py +++ b/aioping/__init__.py @@ -104,7 +104,7 @@ proto_icmp6 = socket.getprotobyname("ipv6-icmp") -def checksum(buffer): +def _checksum(buffer): """ 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 @@ -136,7 +136,7 @@ def checksum(buffer): return answer -async def receive_one_ping(my_socket, id_, timeout): +async def _receive_one_ping(my_socket, id_, timeout): """ receive the ping from the socket. :param my_socket: @@ -180,7 +180,7 @@ async def receive_one_ping(my_socket, id_, timeout): raise TimeoutError("Ping timeout") -def sendto_ready(packet, socket, future, dest): +def _sendto_ready(packet, socket, future, dest): try: socket.sendto(packet, dest) except (BlockingIOError, InterruptedError): @@ -193,7 +193,7 @@ def sendto_ready(packet, socket, future, dest): future.set_result(None) -async def send_one_ping(my_socket, dest_addr, id_, timeout, family): +async def _send_one_ping(my_socket, dest_addr, id_, timeout, family): """ Send one ping to the given >dest_addr<. :param my_socket: @@ -215,7 +215,7 @@ async def send_one_ping(my_socket, dest_addr, id_, timeout, family): data = struct.pack("d", default_timer()) + data.encode("ascii") # Calculate the checksum on the data and the dummy header. - my_checksum = checksum(header + data) + 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. @@ -225,7 +225,7 @@ async def send_one_ping(my_socket, dest_addr, id_, timeout, family): packet = header + data future = asyncio.get_event_loop().create_future() - callback = functools.partial(sendto_ready, packet=packet, socket=my_socket, dest=dest_addr, future=future) + callback = functools.partial(_sendto_ready, packet=packet, socket=my_socket, dest=dest_addr, future=future) asyncio.get_event_loop().add_writer(my_socket, callback) await future @@ -282,8 +282,8 @@ async def ping(dest_addr, timeout=10, family=None): my_id = uuid.uuid4().int & 0xFFFF - await send_one_ping(my_socket, addr, my_id, timeout, family) - delay = await receive_one_ping(my_socket, my_id, timeout) + await _send_one_ping(my_socket, addr, my_id, timeout, family) + delay = await _receive_one_ping(my_socket, my_id, timeout) my_socket.close() return delay From d44c476d3eeb7bf5ec55fe86527f91fd6ca3e41b Mon Sep 17 00:00:00 2001 From: Crypto-Spartan Date: Sat, 18 Jul 2020 02:08:49 -0400 Subject: [PATCH 35/40] return of ping() now in milliseconds --- aioping/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aioping/__init__.py b/aioping/__init__.py index 68f6140..e1d3ee0 100644 --- a/aioping/__init__.py +++ b/aioping/__init__.py @@ -170,7 +170,7 @@ async def _receive_one_ping(my_socket, id_, timeout): data = rec_packet[offset + 8:offset + 8 + struct.calcsize("d")] time_sent = struct.unpack("d", data)[0] - return time_received - time_sent + return (time_received - time_sent) * 1000 except asyncio.TimeoutError: asyncio.get_event_loop().remove_writer(my_socket) From 3cb8aa79a07d34c15d38e88c810744e9e31ec9be Mon Sep 17 00:00:00 2001 From: Crypto-Spartan Date: Sat, 18 Jul 2020 02:09:35 -0400 Subject: [PATCH 36/40] add multiping functions, fix delay in verbose_ping() --- aioping/__init__.py | 26 +++++++++++++++++++++++++- aioping/tests/test_aioping.py | 2 +- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/aioping/__init__.py b/aioping/__init__.py index e1d3ee0..f9e659c 100644 --- a/aioping/__init__.py +++ b/aioping/__init__.py @@ -310,5 +310,29 @@ async def verbose_ping(dest_addr, timeout=2, count=3, family=None): break if delay is not None: - delay *= 1000 logger.info("%s get ping in %0.4fms" % (dest_addr, delay)) + + +async def _do_multiping(dest_addr, timeout=5, family=None): + try: + delay = await ping(dest_addr, timeout, family) + return (dest_addr, delay) + + except TimeoutError: + return (dest_addr, 'TimeoutError') + + +async def _multiping_sem(dest_addr, sem, timeout=5, family=None): + async with sem: + return await _do_ping_sem(dest_addr, timeout, family) + + +async def multiping(dest_addr, timeout=5, family=None): + if len(dest_addr) > 255: + sem = asyncio.Semaphore(255) + tasks = [_multiping_sem(x, sem, timeout, family) for x in dest_addr] + else: + tasks = [_do_multiping(x, timeout, family) for x in dest_addr] + + return await asyncio.gather(*tasks) + diff --git a/aioping/tests/test_aioping.py b/aioping/tests/test_aioping.py index 09ff87f..95fd5ba 100644 --- a/aioping/tests/test_aioping.py +++ b/aioping/tests/test_aioping.py @@ -1,6 +1,6 @@ from unittest import TestCase import asyncio -from aioping import verbose_ping, ping +from aioping import verbose_ping, ping, multiping import logging import socket From 2537be2c0c4228ace82123f11f57c2b4b8cefc72 Mon Sep 17 00:00:00 2001 From: Crypto-Spartan Date: Sat, 18 Jul 2020 02:14:54 -0400 Subject: [PATCH 37/40] update setup.py to 0.4.0 --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index cf47625..638fa9f 100644 --- a/setup.py +++ b/setup.py @@ -4,13 +4,13 @@ setup( name="aioping", packages=["aioping"], - version="0.3.1", + version="0.4.0", install_requires=["async_timeout", "aiodns"], description="Asyncio ping implementation", author="Anton Belousov", author_email="anton@stellarbit.com", url="https://github.com/stellarbit/aioping", - download_url="https://github.com/stellarbit/aioping/tarball/0.3.1", + download_url="https://github.com/stellarbit/aioping/tarball/0.4.0", keywords=["network", "icmp", "ping", "asyncio"], classifiers=[ "Development Status :: 4 - Beta", From e0b220db55057a856e79dd78a6bacbed40520360 Mon Sep 17 00:00:00 2001 From: Crypto-Spartan Date: Sat, 18 Jul 2020 02:26:35 -0400 Subject: [PATCH 38/40] add comments to functions --- aioping/__init__.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/aioping/__init__.py b/aioping/__init__.py index f9e659c..6d0a227 100644 --- a/aioping/__init__.py +++ b/aioping/__init__.py @@ -314,23 +314,40 @@ async def verbose_ping(dest_addr, timeout=2, count=3, family=None): async def _do_multiping(dest_addr, timeout=5, family=None): - try: - delay = await ping(dest_addr, timeout, family) - return (dest_addr, delay) + """ + Execute the ping of a single address for multiping function + """ + + try: + delay = await ping(dest_addr, timeout, family) + return (dest_addr, delay) - except TimeoutError: - return (dest_addr, 'TimeoutError') + except TimeoutError: + return (dest_addr, 'TimeoutError') async def _multiping_sem(dest_addr, sem, timeout=5, family=None): + """ + run the multiping with asyncio.Semaphore limit of 255 + """ + async with sem: return await _do_ping_sem(dest_addr, timeout, family) async def multiping(dest_addr, timeout=5, family=None): + """ + Returns tuple (dest_addr, delay) for each ip address + or domain submitted in a list. Will return + (dest_addr, 'TimeoutError') if ping times out. + """ + + # limit because of select() if len(dest_addr) > 255: sem = asyncio.Semaphore(255) tasks = [_multiping_sem(x, sem, timeout, family) for x in dest_addr] + + # no limit if pinging less than 255 addresses else: tasks = [_do_multiping(x, timeout, family) for x in dest_addr] From 9f008fdd7b434f9f5d7925e0155bd95eaee9e0e3 Mon Sep 17 00:00:00 2001 From: Crypto-Spartan Date: Fri, 30 Oct 2020 02:08:59 -0400 Subject: [PATCH 39/40] change version slightly because of modifying previous commits --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 638fa9f..1ffcdc7 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ setup( name="aioping", packages=["aioping"], - version="0.4.0", + version="0.4.1", install_requires=["async_timeout", "aiodns"], description="Asyncio ping implementation", author="Anton Belousov", From 36a09fabc4da4ce9b4dd9ce98e410f9a35d71922 Mon Sep 17 00:00:00 2001 From: Crypto-Spartan Date: Fri, 30 Oct 2020 02:19:26 -0400 Subject: [PATCH 40/40] updated setup.py for 0.4.1 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 1ffcdc7..cb9a8f5 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ author="Anton Belousov", author_email="anton@stellarbit.com", url="https://github.com/stellarbit/aioping", - download_url="https://github.com/stellarbit/aioping/tarball/0.4.0", + download_url="https://github.com/stellarbit/aioping/tarball/0.4.1", keywords=["network", "icmp", "ping", "asyncio"], classifiers=[ "Development Status :: 4 - Beta",