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 ------- diff --git a/aioping/__init__.py b/aioping/__init__.py index d594d62..6d0a227 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: @@ -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) @@ -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 @@ -310,5 +310,46 @@ 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): + """ + 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') + + +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] + + 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 diff --git a/setup.py b/setup.py index cf47625..cb9a8f5 100644 --- a/setup.py +++ b/setup.py @@ -4,13 +4,13 @@ setup( name="aioping", packages=["aioping"], - version="0.3.1", + version="0.4.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.1", + download_url="https://github.com/stellarbit/aioping/tarball/0.4.1", keywords=["network", "icmp", "ping", "asyncio"], classifiers=[ "Development Status :: 4 - Beta",