Code Monkey home page Code Monkey logo

icmplib's People

Contributors

patrickfnielsen avatar scoulondre avatar valentinbelyn avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

icmplib's Issues

Multiping

Dear Valentin,
Can you add to a icmplib library function receive MAC address during multiping?
Thanks.

Windows host only see final hop

I'm running icmplib on a windows 10 laptop with the basic traceroute example code and what the code is reporting back is just the final hop, the list has a single entry. However if I run up wireshark I'm seeing three pings and their corresponding TTL exceeded reply messages. So for whatever reason the only response that seems to be making it up the stack is the echo reply not the TTL exceeded messages.

Timeout does not trigger soon enough if count > 1

I think this is the cause of home-assistant/core#40232

The timeout is being linked to the count.

>>> import time
>>> from icmplib import ping
>>> time.time() ; ping("1.2.3.4", timeout=2, count=30) ; time.time()
1602340930.801749
<Host [1.2.3.4]>
1602341005.2304416
>>> 
>>> time.time() ; ping("1.2.3.4", timeout=2) ; time.time()
1602340720.058677
<Host [1.2.3.4]>
1602340728.365908
>>> time.time() ; ping("1.2.3.4", timeout=1) ; time.time()
1602340734.0859458
<Host [1.2.3.4]>
1602340738.0910802
>>> 

The result of traceroute is always len(hops) = 1

Hi,

I use python 3.8.2 on windows and everytime I try to execute traceroute I'll get a len(hops) == 1

Are there any compatibiliy issues on Windows systems?

Environment:
Win 10 Pro
Python 3.8.2

Thanks for your help and

regards,

Coffeemaster

Error on ipv6

Thank you so much for your effort on icmplib.
I want to send traceroute with ipv6.

I tried with
: hops = traceroute('2620:100:6040:18::a27d:f812', timeout=1, fast=True, family=6)

But I got the error:

temp18@temp18-Predator-PH315-52:~/ECN_project/icmplib/examples$ sudo python3 traceroute.py Traceback (most recent call last): File "traceroute.py", line 20, in <module> hops = traceroute('2620:100:6040:18::a27d:f812', timeout=1, fast=True, family=6) File "/home/temp18/.local/lib/python3.6/site-packages/icmplib/traceroute.py", line 178, in traceroute **kwargs) TypeError: __init__() got an unexpected keyword argument 'family'

Cloud you please help on this? Thank you.

setsockopt on ICMPSocket

I have a ICMPSocket. I have to use a .setsockopt(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, b"iface") on the underlying socket. What is the recommended way to do that?

  1. s = ICMPv6Socket(); s._sock.setsockopt(...) makes an access to a private attribute.
  2. Subclassing ICMPSocket and add such a functionality is possible and easy; but why is it not done in icmplib itself then?
    a. Provide a wrapper for each possible option (as _set_ttl and _set_traffic_class) is too heavy as the number of supported options increase
    b. Provide a proxy method (icmpsocket.setsockopt(...)) that just forwards the call to self._sock.setsockopt(...) is possible, while it adds a function call.
    c. Change self._sock to a public attribute is another possible solution.
  3. Don't do that at all, icmplib won't support it (which I cannot accept for my personal project—but that is my personal problem then. 😉 )

I would prefer 2.b supported by icmplib.

Using icmplib without root permission

Is it possible to use this library without root permission?

I'm using Kali Linux kali 5.16.0-kali1-amd64 #1 SMP PREEMPT Debian 5.16.7-2kali1 (2022-02-10) x86_64 GNU/Linux with Python 3.9 and icmplib-3.0.3. I can run ping in my terminal. I've checked

$ ls -l $(which ping)
-rwxr-xr-x 1 root root 81600 Feb  5 05:37 /usr/bin/ping

As you can see, SUID and SGID isn't set. I've checked

$ sysctl net.ipv4.ping_group_range
net.ipv4.ping_group_range = 1   0

Obviously, ping can be run without root permission.

I've tried

from icmplib import ping
ping('192.168.178.1', privileged=False)

and got

Traceback (most recent call last):
  File "/workdir/lib/python3.9/site-packages/icmplib/sockets.py", line 88, in __init__
    self._sock = self._create_socket(
  File "/workdir/lib/python3.9/site-packages/icmplib/sockets.py", line 486, in _create_socket
    return socket.socket(
  File "/usr/lib/python3.9/socket.py", line 232, in __init__
    _socket.socket.__init__(self, family, type, proto, fileno)
PermissionError: [Errno 13] Permission denied

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/workdir/main.py", line 5, in <module>
    ping('192.168.178.1', privileged=False)
  File "/workdir/lib/python3.9/site-packages/icmplib/ping.py", line 141, in ping
    with _Socket(source, privileged) as sock:
  File "/workdir/lib/python3.9/site-packages/icmplib/sockets.py", line 97, in __init__
    raise SocketPermissionError(privileged)
icmplib.exceptions.SocketPermissionError: A prior configuration of your OS is required to use ICMP sockets without root privileges. Read more on https://github.com/ValentinBELYN/icmplib

I don't have permissions to change

sudo sysctl -w net.ipv4.ping_group_range='0 2147483647'

Is there a way to use this library? Is this problem related to the way this library works or is it a general Python problem?

TTL field and DF flag addition

Hello and thank you for your development of icmplib.

I'm using it as part of a tool for diagnostic purposes. I wanted to ask if it is possible to include these two parameters as part of future development. First one is a "df" parameter as a feature, so that if set to True, will enable the Don't Fragment flag in the IP header. Second one is a ttl return value, corresponding to the ttl field value in received packet. Or maybe there is something related to the above but I'm missing them.

Thanks again and regards.

Privileged Mode failing on MacOS

Hi,
First of all, thank you for the library. I have been testing PING and Multiping, but it seems the privileged attribute is not working on macOS 11.6.5 with python 3.9.7

I'm calling the function as this

hosts = multiping( devices, count=3,interval=0.5,privileged=False)

if I used python as regular user, then I got

PermissionError: [Errno 1] Operation not permitted

no matter if I set ptivileged True or False.

If I try to use sudo python (to elevate privilege) then I got this error

request = ICMPRequest(TypeError: __init__() got an unexpected keyword argument 'privileged

again it doesn't matter if I passed True or False to the argument.

The same behavior is with ping function. With this sentence

host = ping('10.74.200.1', privileged=False)
an error is raised,

icmplib.exceptions.SocketPermissionError: Root privileges are required to create the socket

and also ther is no effect on change the privileged argument to True or False.

this are the version installed

icmplib==3.0.3
icmplibv2==1.0.6

Thanks again for any hints to this situation

Non-root traceroute

Works for macOS 10.15 (with patch):

>>> print(icmplib.traceroute("www.google.com", privileged=False))
[<Hop 1 [...]>, <Hop 2 [...]>, <Hop 3 [...]>, <Hop 4 [188.234.131.158]>, <Hop 5 [188.234.131.159]>, <Hop 6 [172.253.76.91]>, <Hop 7 [74.125.244.181]>, <Hop 8 [72.14.232.84]>, <Hop 9 [142.251.61.219]>, <Hop 10 [142.250.56.129]>, <Hop 20 [64.233.162.147]>]
>>> 

PS. works for linux too but returns list with last hop only

AttributeError: module 'socket' has no attribute 'IPPROTO_ICMPV6'

Hi,

I came across you're package and was hoping to check multiple servers whether they're up or not.

So, I went for multiping as suggested in the docs. The problem is when the socket opens it gives the error

AttributeError: module 'socket' has no attribute 'IPPROTO_ICMPV6'

I went snooping in the code base, it refers to line 579 in sockets.py

return socket.socket(
family=socket.AF_INET6,            
type=type,            
proto=socket.IPPROTO_ICMPV6)

I've tried the module with both py 3.7.9 and 3.9.5. Works perfectly fine in 3.9.5 but gives the error in 3.7.9.

I changed the codebase in 3.7.9, with

IPPROTO_ICMP

and it worked.

So my request is if we can amend the current codebase to

try:

return socket.socket(
family=socket.AF_INET6,            
type=type,            
proto=socket.IPPROTO_ICMPV6)

except AttributeError:

return socket.socket(
family=socket.AF_INET6,            
type=type,            
proto=socket.IPPROTO_ICMP)

let me know if it is possible. or any help required from my side.

Also, on side, haven't gone through the whole code base but needed some guidance, will be multiping be quicker on roughly 70 servers, or should I think about async socket programming? PS: the client wants the result after every 30 seconds.

Thanks in advance!

UnboundLocalError: local variable 'hop' referenced before assignment

Using your library I am having issues with the traceroute portion. Below is my code. I feel like I have practically followed your example besides using input instead of a pre-defined IP.

from icmplib import ping, multiping, traceroute, Host, Hop
def trace(addresses):
for address in addresses:
hops = traceroute(
"{address}", count=3, interval=0.5, timeout=2, max_hops=10, fast_mode=False,
)
print(address)
print("Distance (ttl) Address Average round-trip time")
last_distance = 0
for hop in hops:
if last_distance + 1 != hop.distance:
print("****")
print(f"{hop.distance} {hop.address} {hop.avg_rtt} ms")
last_distance = hop.distance

I am a beginner so maybe I am doing something wrong but it seems like hop isn't defined in your library.

Proposal to improve the resolve function

Looking at the is_ipv4_address method, it seems it uses a somewhat simple method to test for an IPv4 address. Testing 999.999.999.999 would evaluate to True and can lead to resolve assuming that the address is valid.

If I may suggest a solution, you could skip the entirety of the manual work and rely solely on socket.getaddrinfo which will return the address immediately if it does not require resolution:

I'm using time.time() here to show whether the resolution involves a DNS server or not, but I did check with tcpdump.

>>> import socket
>>> import time
>>> 
>>> def resolve(host):
...     checkpoint = time.time()
...     try:
...             print(socket.getaddrinfo(host, port=None)[0][4][0])
...     except OSError:
...             print('failed to resolve')
...     print('took', time.time() - checkpoint)
... 
>>> resolve('9.9.9.9')
9.9.9.9
took 0.00821995735168457
>>> 
>>> resolve ('666.666.666.666')
failed to resolve
took 0.031622886657714844

This will make it so is_ipv4_address and is_ipv6_address are no longer required as the tests are performed by socket.

In addition, there is no way to specify a preferred address family and IPv4 is a forced-defaulted. When an OS has IPv4 and IPv6 routing (i.e. default route over both families), it will prefer IPv6 when calling socket.getaddrinfo by default as it is the newer version.

As such, I would suggest that the resolve function have a keyword argument that defaults to None, but can be set to either 4 or 6 to force a specific address family.

For example:

def resolve(name, address_family=None):
    if address_family == 4:
        # Force IPv4-only
        address_family = socket.AF_INET
    elif address_family == 6:
        # Force IPv6-only
        address_family = socket.AF_INET6
    else:
        # This lets the OS pick which is best (i.e. IPv6 if it has connectivity)
        address_family = None

    try:
        return socket.getaddrinfo(
            host=name,
            port=None,
            family=address_family,
        )[0][4][0]
    except OSError:
        raise NameLookupError(name)

P.S. Great work on the library

hosts > 1024

Issue

hosts = ['172.20.20.1','172.20.20.2','172.20.20.3',...] # 1173 items in the list

result = await async_multiping(hosts, count=2, timeout=1, interval=0.4, privileged=False, payload_size=16)
for item in result:
    pass

Output

------------------------------------------------------------
  172.20.20.62
------------------------------------------------------------
  Packets sent:     2
  Packets received: 2
  Packet loss:      0.0%
  Round-trip times: 2.74 ms / 410.135 ms / 817.53 ms
  Jitter:           814.79 ms
------------------------------------------------------------
  172.20.20.80
------------------------------------------------------------
  Packets sent:     2
  Packets received: 2
  Packet loss:      0.0%
  Round-trip times: 1.934 ms / 2.99 ms / 4.046 ms
  Jitter:           2.113 ms
------------------------------------------------------------
  172.20.20.65
------------------------------------------------------------
  Packets sent:     0
  Packets received: 0
  Packet loss:      0.0%
  Round-trip times: 0.0 ms / 0.0 ms / 0.0 ms
  Jitter:           0.0 ms
------------------------------------------------------------
  172.20.20.81
------------------------------------------------------------
  Packets sent:     0
  Packets received: 0
  Packet loss:      0.0%
  Round-trip times: 0.0 ms / 0.0 ms / 0.0 ms
  Jitter:           0.0 ms
------------------------------------------------------------
  172.20.20.63
------------------------------------------------------------
  Packets sent:     0
  Packets received: 0
  Packet loss:      0.0%
  Round-trip times: 0.0 ms / 0.0 ms / 0.0 ms
  Jitter:           0.0 ms

If there are more than 1024 addresses, then in the answer I also get instances with standard (empty) data.
Or are there options for how this can be handled?

ICMP server mode support

Thanks for bringing up such a sleek library.
This icmplib seems to only support ICMP client mode where the ping is initiated.
It would be better if it could support the ICMP server mode as well.
Cheers.

icmplib fails to ping hostnames that only resolve to AAAA entries

# docker-compose exec home-assistant bash
bash-5.0# /usr/local/bin/python3
Python 3.8.5 (default, Sep 10 2020, 14:23:57)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from icmplib import ping
>>> ping('192.168.1.1')
<Host [192.168.1.1]>
>>> host = ping('192.168.1.1')
>>> host.is_alive
True
>>> host = ping('foo:bar:baz:1::101')
>>> host.is_alive
True
>>> host = ping('only-aaaa.example.org')
>>> host.is_alive
False
bash-5.0# ping6 -c 1 only-aaaa.example.org
PING only-aaaa.example.org (foo:bar:baz:1::101): 56 data bytes
64 bytes from foo:bar:baz:1::101: seq=0 ttl=62 time=0.981 ms

You can probably reproduce the same with ipv6.google.com:

bash-5.0# host ipv6.google.com
ipv6.google.com is an alias for ipv6.l.google.com.
ipv6.l.google.com has IPv6 address 2a00:1450:400a:800::200e
bash-5.0# ping6 ipv6.google.com
PING ipv6.google.com (2a00:1450:400a:802::200e): 56 data bytes
64 bytes from 2a00:1450:400a:802::200e: seq=0 ttl=111 time=10.882 ms
^C
--- ipv6.google.com ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 10.882/10.882/10.882 ms
bash-5.0# pip list | grep icmplib
icmplib                          1.1.3
bash-5.0# python3
Python 3.8.5 (default, Sep 10 2020, 14:23:57)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from icmplib import ping
>>> host = ping('ipv6.google.com')
>>> host.is_alive
False
>>>

Issue originally reported here: home-assistant/core#40232 (comment)

Bug: struct.error: unpack requires a buffer of 4 bytes

I am running a python script that pings a host in a specific interval.
From time to time (~5 times a day) I get this error:

File "/home/asd/venv/asd/lib/python3.7/site-packages/icmplib/ping.py", line 123, in ping
      reply = socket.receive()
   File "/home/asd/venv/asd/lib/python3.7/site-packages/icmplib/sockets.py", line 313, in receive
      reply_time=reply_time)
   File "/home/asd/venv/asd/lib/python3.7/site-packages/icmplib/sockets.py", line 210, in _read_reply
      self._config.ICMP_PAYLOAD_OFFSET
struct.error: unpack requires a buffer of 4 bytes

Here is the line calling icmplib:

icmp_ping(SOME_IP, 3)

Ping times are slightly off on loaded systems

recvmsg(3, {msg_name={sa_family=AF_INET, sin_port=htons(0), sin_addr=inet_addr("192.168.208.1")}, msg_namelen=128->16, msg_iov=[{iov_base="E\0\0T\220\242\0\0@\1\310\256\300\250\320\1\300\250\320\5\0\0a\251 D\0\1\230\ff_"..., iov_len=192}], msg_iovlen=1, msg_control=[{cmsg_len=32, cmsg_level=SOL_SOCKET, cmsg_type=SCM_TIMESTAMP, cmsg_data={tv_sec=1600523416, tv_usec=512848}}], msg_controllen=32, msg_flags=0}, 0) = 84

It looks like the ping binary looks at the cmsg_type and retrieves the timestamp from cmsg_data to give a more accurate time.

icmplib currently gets time using time() which is subject to fluctuations due to cpu load.

ping(..., privileged=False) not working as expected

Tried launching a ping with the privileged=False option, but still throws a SocketPermissionError:

>>> ping("8.8.8.8", privileged=False)
Traceback (most recent call last):
  File "/home/david/.miniconda3/envs/ping2mqtt/lib/python3.8/site-packages/icmplib/sockets.py", line 88, in __init__
    self._sock = self._create_socket(
  File "/home/david/.miniconda3/envs/ping2mqtt/lib/python3.8/site-packages/icmplib/sockets.py", line 452, in _create_socket
    return socket.socket(
  File "/home/david/.miniconda3/envs/ping2mqtt/lib/python3.8/socket.py", line 231, in __init__
    _socket.socket.__init__(self, family, type, proto, fileno)
PermissionError: [Errno 13] Permission denied

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/david/.miniconda3/envs/ping2mqtt/lib/python3.8/site-packages/icmplib/ping.py", line 131, in ping
    sock = ICMPv4Socket(
  File "/home/david/.miniconda3/envs/ping2mqtt/lib/python3.8/site-packages/icmplib/sockets.py", line 97, in __init__
    raise SocketPermissionError
icmplib.exceptions.SocketPermissionError: Root privileges are required to create the socket

System details:

  • OS: Debian 10 buster
  • Kernel: x86_64 Linux 4.19.0-16-amd64
  • Python version: 3.8.8 - through miniconda 4.8.4
  • icmplib version: 2.1.1

Also tried with Python 3.9.4 from within a Docker container, running as root, and when setting the privileged=False setting, the outcome is the same:

>>> from icmplib import ping
>>> ping("8.8.8.8", privileged=False)
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/icmplib/sockets.py", line 88, in __init__
    self._sock = self._create_socket(
  File "/usr/local/lib/python3.9/site-packages/icmplib/sockets.py", line 452, in _create_socket
    return socket.socket(
  File "/usr/local/lib/python3.9/socket.py", line 232, in __init__
    _socket.socket.__init__(self, family, type, proto, fileno)
PermissionError: [Errno 13] Permission denied

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.9/site-packages/icmplib/ping.py", line 131, in ping
    sock = ICMPv4Socket(
  File "/usr/local/lib/python3.9/site-packages/icmplib/sockets.py", line 97, in __init__
    raise SocketPermissionError
icmplib.exceptions.SocketPermissionError: Root privileges are required to create the socket
>>> ping("8.8.8.8")
<Host [8.8.8.8]>

multiping exception with failed hostname resolve

Hi!

I am having troubles (see home-assistant/core#60781) with the library being used by home-assistant. The device tracker uses the library to ping device to determine if they are available. The problem is, if a name does not resolve, an exception is thrown and no device is marked "reachable" as there is no return at all.

I saw the comment that hostnames are not recommended (unfortunately no further explanation).
Would that mean for the home-assistant integration to first run a hostname resolve to see what is resolvable and then do a ping only for those to avoid exceptions or is there an exception handling missing in the library?

Any help would be appreciated to determine where the issue could be resolved. Thanks!

multiping throws an exception if any of the hosts are not resolveable

>>> icmplib.multiping(["ping.lrz.de", "192.168.1.1"])
[<Host [129.187.10.17]>, <Host [192.168.1.1]>]
>>> icmplib.multiping(["ping.lrz.de", "192.168.1.1", "invalid.invalid"])
Traceback (most recent call last):
  File "/nix/store/kf6rq5lrpbvz9nivynbrjixz1xi36svb-python3.9-icmplib-3.0.2/lib/python3.9/site-packages/icmplib/utils.py", line 158, in async_resolve
    lookup = await loop.getaddrinfo(
  File "/nix/store/wq6s8i407ic4qp1dvd5yhrnd0kflzh6x-python3-3.9.12/lib/python3.9/asyncio/base_events.py", line 861, in getaddrinfo
    return await self.run_in_executor(
  File "/nix/store/wq6s8i407ic4qp1dvd5yhrnd0kflzh6x-python3-3.9.12/lib/python3.9/concurrent/futures/thread.py", line 58, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/nix/store/wq6s8i407ic4qp1dvd5yhrnd0kflzh6x-python3-3.9.12/lib/python3.9/socket.py", line 954, in getaddrinfo
    for res in _socket.getaddrinfo(host, port, family, type, proto, flags):
socket.gaierror: [Errno -2] Name or service not known

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/nix/store/kf6rq5lrpbvz9nivynbrjixz1xi36svb-python3.9-icmplib-3.0.2/lib/python3.9/site-packages/icmplib/multiping.py", line 267, in multiping
    return asyncio.run(
  File "/nix/store/wq6s8i407ic4qp1dvd5yhrnd0kflzh6x-python3-3.9.12/lib/python3.9/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/nix/store/wq6s8i407ic4qp1dvd5yhrnd0kflzh6x-python3-3.9.12/lib/python3.9/asyncio/base_events.py", line 647, in run_until_complete
    return future.result()
  File "/nix/store/kf6rq5lrpbvz9nivynbrjixz1xi36svb-python3.9-icmplib-3.0.2/lib/python3.9/site-packages/icmplib/multiping.py", line 163, in async_multiping
    return [task.result() for task in tasks]
  File "/nix/store/kf6rq5lrpbvz9nivynbrjixz1xi36svb-python3.9-icmplib-3.0.2/lib/python3.9/site-packages/icmplib/multiping.py", line 163, in <listcomp>
    return [task.result() for task in tasks]
  File "/nix/store/kf6rq5lrpbvz9nivynbrjixz1xi36svb-python3.9-icmplib-3.0.2/lib/python3.9/site-packages/icmplib/ping.py", line 263, in async_ping
    address = (await async_resolve(address, family))[0]
  File "/nix/store/kf6rq5lrpbvz9nivynbrjixz1xi36svb-python3.9-icmplib-3.0.2/lib/python3.9/site-packages/icmplib/utils.py", line 168, in async_resolve
    return await async_resolve(name, 6)
icmplib.exceptions.NameLookupError: The name 'invalid.invalid' cannot be resolved

My hope would be that even if one of the names cannot be looked up, a result would be returned for the others, and potentially that a result indicating the lookup failure would be given back for the unresolveable name.

traceroute: last hop ip different with dest address in output.

Hi,

when i use traceroute module, the last address not my address in output, does it mean the address can't reach ?
And Hop 3 to Hop 21 means 4~20 not response ?

Example:

Python 3.8.5 (default, Sep 22 2020, 23:34:13)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-36)] on linux
Type "help", "copyright", "credits" or "license" for more information.

from icmplib import *
traceroute("192.168.46.25")
[<Hop 1 [193.22.152.1]>, <Hop 2 [172.16.0.11]>, <Hop 3 [192.168.101.42]>, <Hop 21 [38.142.238.113]>]


ping requires root privileges

I'm using debian 10 with python3 3.7.3 and pinging doesn't work without being root. This is what I've tried:
Package version is 2.0.1

Python 3.7.3 (default, Jul 25 2020, 13:03:44)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from icmplib import ping
>>> test = ping("8.8.8.8", count=1, interval=1, timeout=1, privileged=False)
Traceback (most recent call last):
  File "/home/max/.local/lib/python3.7/site-packages/icmplib/sockets.py", line 88, in __init__
    socket.SOCK_DGRAM)
  File "/home/max/.local/lib/python3.7/site-packages/icmplib/sockets.py", line 448, in _create_socket
    proto=socket.IPPROTO_ICMP)
  File "/usr/lib/python3.7/socket.py", line 151, in __init__
    _socket.socket.__init__(self, family, type, proto, fileno)
PermissionError: [Errno 13] Permission denied

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/max/.local/lib/python3.7/site-packages/icmplib/ping.py", line 133, in ping
    privileged=privileged)
  File "/home/max/.local/lib/python3.7/site-packages/icmplib/sockets.py", line 95, in __init__
    raise SocketPermissionError
icmplib.exceptions.SocketPermissionError: Root privileges are required to create the socket
>>>

Edit: It works on my other ubuntu server with python 3.8.5.

Bug: [Multiping] The avg_rtt and min_rtt values are sometimes negative

When I test the multiping function, I get the result below:
xxx rtt: min[-4411.397] avg[-4411.397] max[0.0] lose_packet:0.5 packets_received:1
xxx rtt: min[-4625.375] avg[-3237.526] max[0.0] lose_packet:0.0 packets_received:2

Test Case like below:

network = '192.168.10.0/24'
net = ipaddress.ip_network(network)
hosts = []
for ip in net:
    hosts.append(str(ip))
res = multiping(hosts, count=2, timeout=1)
alive_host = []
for ip in res:
    if ip.is_alive:
        alive_host.append((ip.address, ip.avg_rtt))
        if ip.max_rtt < 0 or ip.min_rtt < 0 or ip.avg_rtt < 0:
            print("{} rtt: min[{}] avg[{}] max[{}]  lose_packet:{} packets_received:{}".format(ip.address, ip.min_rtt, ip.avg_rtt, ip.max_rtt, ip.packet_loss, ip.packets_received))

Ping requests with source address - problem

I was using the source parameter on icmplib.ping with the intention of sending a ping request via different interfaces to the same destination. With Linux command line ping you can specify the -I parameter to be either a interface name or an IP address and the first option can be used to get the desired effect, but one can't do this with icmplib.ping. If one merely specifies the desired source address then the ping goes out whatever interface is the default route for the target address which wont be appropriate. It is of course possible to circumvent the problem by matching the source address with an ip rule and directing it to the appropriate interface but this does start to build in dependencies on whatever ip address the interface happens to have and leads to unnecessary complication. Any chance of an option to specify the interface required?

Support for zone index in IPv6 addresses

Current implementation does not support zone index of IPv6 addresses (...%if part to designate interface). Here, I try to ping fe80:aaaa:bbbb:cccc:dddd%wlo1, which clearly exists (it is my own interface's address on wifi).

Using the standard ping command, no problem:

$ ping -c2 fe80::aaaa:bbbb:cccc:dddd%wlo1
PING fe80::aaaa:bbbb:cccc:dddd%wlo1(fe80::aaaa:bbbb:cccc:dddd%wlo1) 56 octets de données
64 octets de fe80::aaaa:bbbb:cccc:dddd%wlo1 : icmp_seq=1 ttl=64 temps=0.026 ms
64 octets de fe80::aaaa:bbbb:cccc:dddd%wlo1 : icmp_seq=2 ttl=64 temps=0.026 ms

--- statistiques ping fe80::aaaa:bbbb:cccc:dddd%wlo1 ---
2 paquets transmis, 2 reçus, 0% packet loss, time 1017ms
rtt min/avg/max/mdev = 0.026/0.026/0.026/0.000 ms

Using icmplib:

In [1]: import icmplib
   ...: import socket

In [2]: s = icmplib.ICMPv6Socket(privileged=False)
   ...: request = icmplib.ICMPRequest("fe80::aaaa:bbbb:cccc:dddd%wlo1", 0, 4)
   ...: s.send(request)
---------------------------------------------------------------------------
OSError                                   Traceback (most recent call last)
~/.local/lib/python3.9/site-packages/icmplib/sockets.py in send(self, request)
    271             request._time = time()
--> 272             self._sock.sendto(packet, (request.destination, 0))
    273

OSError: [Errno 22] Invalid argument

During handling of the above exception, another exception occurred:

ICMPSocketError                           Traceback (most recent call last)
<ipython-input-2-daa259b33195> in <module>
      1 s = icmplib.ICMPv6Socket(privileged=False)
      2 request = icmplib.ICMPRequest("fe80::aaaa:bbbb:cccc:dddd%wlo1", 0, 4)
----> 3 s.send(request)

~/.local/lib/python3.9/site-packages/icmplib/sockets.py in send(self, request)
    283
    284         except OSError as err:
--> 285             raise ICMPSocketError(str(err))
    286
    287     def receive(self, request=None, timeout=2):

ICMPSocketError: [Errno 22] Invalid argument

Basically, s.send forward the request's destination to socket.socket.sendto, in a tuple (address, port/identifier). There, the …%wlo1 part is not allowed:

In [3]: packet = s._create_packet(id=request.id, sequence=request.sequence, payload=b"")
   ...: s._sock.sendto(packet, ("fe80::aaaa:bbbb:cccc:dddd%wlo1", 0))
---------------------------------------------------------------------------
OSError                                   Traceback (most recent call last)
<ipython-input-3-b1659f9707d4> in <module>
      1 packet = s._create_packet(id=request.id, sequence=request.sequence, payload=b"")
----> 2 s._sock.sendto(packet, ("fe80::aaaa:bbbb:cccc:dddd%wlo1", 0))

OSError: [Errno 22] Invalid argument

One may use socket.getaddrinfo to get the correct tuple to forward (here, "3" is the number of my wlo1 interface):

In [4]: dest = socket.getaddrinfo("fe80::aaaa:bbbb:cccc:dddd%wlo1", port=None, family=socket.AF_INET6, type=socket.SOCK_DGRAM)[0][4]
   ...: print(dest)
   ...: s._sock.sendto(packet, dest)
   ...: s.receive()
('fe80::aaaa:bbbb:cccc:dddd', 0, 0, 3)
Out[4]: <ICMPReply [fe80::aaaa:bbbb:cccc:dddd]>

Traceroute on IPv6 Network

Hi, when I try traceroute to any IPv6 address, this lib just put the dst as the only hop.
I have turnoff the firewall on this machine.

Command:

Python 3.6.8 (default, Nov 16 2020, 16:55:22) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-44)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import icmplib
>>> hops = icmplib.traceroute('2001:470:1:18::115')     
>>> print(hops) 
[<Hop 1 [2001:470:1:18::115]>]

TCPDUMP:

07:38:11.602563 IP6 (hlim 64, next-header ICMPv6 (58) payload length: 64) MY_LOCAL_IPV6 > 2001:470:1:18::115: [icmp6 sum ok] ICMP6, echo request, seq 0
07:38:11.662401 IP6 (flowlabel 0xe3c64, hlim 57, next-header ICMPv6 (58) payload length: 64) 2001:470:1:18::115 > MY_LOCAL_IPV6  [icmp6 sum ok] ICMP6, echo reply, seq 0
07:38:11.662839 IP6 (hlim 64, next-header ICMPv6 (58) payload length: 64) MY_LOCAL_IPV6  > 2001:470:1:18::115: [icmp6 sum ok] ICMP6, echo request, seq 1
07:38:11.722478 IP6 (flowlabel 0xe3c64, hlim 57, next-header ICMPv6 (58) payload length: 64) 2001:470:1:18::115 > MY_LOCAL_IPV6  [icmp6 sum ok] ICMP6, echo reply, seq 1

traceroute using linux tools

[root@us-iad03-t2-1g-1 ~]# traceroute 2001:470:1:18::115
traceroute to 2001:470:1:18::115 (2001:470:1:18::115), 30 hops max, 80 byte packets
 1  po001.c25.iad03.us.misaka.io (2a0e:6902:2000::4)  0.275 ms  0.105 ms  0.096 ms
 2  v204.cs01.iad03.us.misaka.io (2a0e:6902:2000::2)  0.289 ms v204.cs02.iad03.us.misaka.io (2a0e:6902:2000::3)  0.287 ms  0.191 ms
 3  v204.cs02.iad03.us.misaka.io (2a0e:6902:2000::3)  0.182 ms 2001:504:31::1b1b:1 (2001:504:31::1b1b:1)  4.288 ms  7.232 ms
 4  2001:504:31::1b1b:1 (2001:504:31::1b1b:1)  10.159 ms  13.229 ms  16.196 ms
 5  100ge14-1.core2.ash1.he.net (2001:470:0:4c4::1)  0.578 ms 100ge1-2.core1.ash1.he.net (2001:470:0:277::1)  22.035 ms 100ge14-1.core2.ash1.he.net (2001:470:0:4c4::1)  0.393 ms
 6  100ge1-2.core1.ash1.he.net (2001:470:0:277::1)  0.591 ms  0.832 ms 100ge7-2.core1.pao1.he.net (2001:470:0:8f::1)  59.183 ms
 7  100ge14-1.core3.fmt1.he.net (2001:470:0:30::1)  72.677 ms  72.549 ms  72.433 ms
 8  100ge14-1.core3.fmt1.he.net (2001:470:0:30::1)  72.324 ms  72.216 ms  67.062 ms
 9  2001:470:1:18::115 (2001:470:1:18::115)  59.648 ms  59.562 ms  59.686 ms

Use monotonic time

It would be better to use monotonic time to calculate timeout in IO logic.

Multiping scale

How may host can we ping using multi ping. I tried with 1000 host. Ping was successful but CPU utilisation was more than 100%?
Is there a known issue?

Problem with traceroute

Hi, sorry for my bad english.
My problem is that I tried your "traceroute.py" example but it doesn't work.
He tells me that "Some routers are not responding" but if I ping from the cmd with various ttl, it works.
Cattura

Responses are not linked to requests

There seems to be a problem with matching incoming responses to requests.
If I

  1. send an ICMP request that times out after 200ms,
  2. send another one at 250 milliseconds, and
  3. get a reply to the first request at 255 milliseconds,

I get a reported round trip time of 5 milliseconds for the second request, even if that probably will take longer.
There should be some kind of mechanism to validate that any response receives actually matches the current request so that previous answers don't throw off the results.

Reverse Lookup?

Hi,
First, thank you. I am enjoying this module immensely! I love that you provide the privilege option. for some of us who have to work in locked down environments, this is key.

I have just updated my ping check script to use icmplib!

You recommend not using FQDNs. May I as why? Most of my inventory files use FQDNs which is why I ask.

This is not an issue but more of a feature request.

Would it be possible to add reverse lookup (get FQDN from IP address)?

Traceroute: avg_rtt should be divided by packets_received

In the traceroute module "avg_rtt" does not return the average but the total round trip.

Looking at how its implemented for the ping module, the following is missing:

    if packets_received:
        avg_rtt /= packets_received

I'll be happy to create a PR if you want, though it's a simple fix.

asyncio support

Hi!

I was porting another ICMP package over to be async when I found this. I would love to create a async def ping which is not blocking and is awaitable, using the current codebase but running the sockets in executors, and using a randint instead of PID.
Let me know if you are open to accept PRs.

The alternative is for me to create my own package that basically only utilizes yours, but has it's own async def ping function.

privileged paramter has no effect

Hi Valentin,

first of all, thank you very much for this nice library. It is exactly, what I am looking for! Yet, I have two small issues... perhaps you can enlighten me.

The first one is the privileged parameter. Please see the error message below... whether I set it to True or False, does not matter -- I get this error.

a = ping("10.10.10.10", count=3, interval=1, timeout=2, privileged=True) File "/home/fl/SW/miniconda/envs/mib/lib/python3.9/site-packages/icmplib/ping.py", line 141, in ping with _Socket(source, privileged) as sock: File "/home/fl/SW/miniconda/envs/mib/lib/python3.9/site-packages/icmplib/sockets.py", line 97, in __init__ raise SocketPermissionError icmplib.exceptions.SocketPermissionError: Root privileges are required to create the socket

I attempted to use icmplib in an anaconda environment with up-to-date packages. The only thing a little unusual is that it is started from inside a thread. Multiping also does not work here, it exits like this:

RuntimeError: can't register atexit after shutdown Task exception was never retrieved future: <Task finished name='Task-11' coro=<async_ping() done, defined at /home/fl/SW/miniconda/envs/mib/lib/python3.9/site-packages/icmplib/ping.py:168> exception=RuntimeError("can't register atexit after shutdown")> Traceback (most recent call last): File "/home/fl/SW/miniconda/envs/mib/lib/python3.9/site-packages/icmplib/ping.py", line 263, in async_ping address = (await async_resolve(address, family))[0] File "/home/fl/SW/miniconda/envs/mib/lib/python3.9/site-packages/icmplib/utils.py", line 158, in async_resolve lookup = await loop.getaddrinfo( File "/home/fl/SW/miniconda/envs/mib/lib/python3.9/asyncio/base_events.py", line 856, in getaddrinfo return await self.run_in_executor( File "/home/fl/SW/miniconda/envs/mib/lib/python3.9/asyncio/base_events.py", line 809, in run_in_executor executor = concurrent.futures.ThreadPoolExecutor( File "/home/fl/SW/miniconda/envs/mib/lib/python3.9/concurrent/futures/__init__.py", line 49, in __getattr__ from .thread import ThreadPoolExecutor as te File "/home/fl/SW/miniconda/envs/mib/lib/python3.9/concurrent/futures/thread.py", line 37, in <module> threading._register_atexit(_python_exit) File "/home/fl/SW/miniconda/envs/mib/lib/python3.9/threading.py", line 1374, in _register_atexit raise RuntimeError("can't register atexit after shutdown") RuntimeError: can't register atexit after shutdown

But multiping is not that important to me and I just wanted to let you know.

Thanks for your help!

Cheers,
Frank

Get TTL value

First of all, I have to say your library is cool. But I'm using this multiping function. Is there anyway to get TTL value here?

callback for multiping,

I propose to implement a callback for the 'multiping' functions, to perform some functions after receiving a response from the host

Using your library as an example

import asyncio

from datetime import datetime
from icmplib import Host, ICMPLibError, ICMPRequest, AsyncSocket, ICMPv4Socket, ICMPv6Socket
from icmplib.utils import unique_identifier, is_ipv6_address, async_resolve, is_hostname

from test_funcs import getHosts


async def async_ping(address, count=4, interval=1, timeout=2, id=None,
                     source=None, family=None, privileged=True, callback_func=None, **kwargs):
    if is_hostname(address):
        address = (await async_resolve(address, family))[0]

    if is_ipv6_address(address):
        _Socket = ICMPv6Socket
    else:
        _Socket = ICMPv4Socket

    id = id or unique_identifier()
    packets_sent = 0
    rtts = []

    with AsyncSocket(_Socket(source, privileged)) as sock:
        for sequence in range(count):
            if sequence > 0:
                await asyncio.sleep(interval)

            request = ICMPRequest(
                destination=address,
                id=id,
                sequence=sequence,
                **kwargs)

            try:
                sock.send(request)
                packets_sent += 1

                reply = await sock.receive(request, timeout)
                reply.raise_for_status()

                rtt = (reply.time - request.time) * 1000
                rtts.append(rtt)

            except ICMPLibError:
                pass
    if callback_func:
        return await callback_func(Host(address, packets_sent, rtts))

    return Host(address, packets_sent, rtts)


async def async_multiping(addresses, count=2, interval=0.5, timeout=2,
                          concurrent_tasks=50, source=None, family=None, privileged=True,
                          callback_func=None, **kwargs):
    loop = asyncio.get_running_loop()
    tasks = []
    tasks_pending = set()

    for address in addresses:
        if len(tasks_pending) >= concurrent_tasks:
            _, tasks_pending = await asyncio.wait(
                tasks_pending,
                return_when=asyncio.FIRST_COMPLETED)

        task = loop.create_task(
            async_ping(
                address=address,
                count=count,
                interval=interval,
                timeout=timeout,
                source=source,
                family=family,
                privileged=privileged,
                callback_func=callback_func,
                **kwargs)
        )

        tasks.append(task)
        tasks_pending.add(task)

    await asyncio.wait(tasks_pending)

    return [task.result() for task in tasks]


async def callback(host):
    print(host.address)
    # send_me(host) example func
    return host

async def test():
    startTime = datetime.now()

    hosts = await getHosts()

    hostsDown = []
    hostsUp = []

    for result in await async_multiping(
            hosts, count=1, timeout=1, interval=0.4, privileged=False, payload_size=8, callback_func=callback
    ):
        if result.is_alive:
            hostsUp.append(result)
        else:
            hostsDown.append(result)

    exTime = (datetime.now() - startTime).total_seconds()

    print(' -', len(hostsUp), 'hosts up out of', len(hosts))

    print(' -', 'execution speed', exTime, 'secs')


asyncio.run(test())

Output

172.20.3.12
172.20.3.198
172.20.3.97
172.20.4.136
172.20.4.184
 - 1160 hosts up out of 1173
 - execution speed 1.371824 secs

Socket recvfrom buffer too small for receiving responses to large ICMP echo requests

Issue

In icmplib/sockets.py, the response buffer allocated by the receive method isn't big enough, when the echo request payload_size is greater than 996 bytes.

response = self._sock.recvfrom(1024)

Using examples/ping.py for testing:

  • On Ubuntu (root), the calculated bytes_received value is incorrect, as response is truncated.

  • On Windows 10, the self._sock.recvfrom(1024) triggers the below exception, which is swallowed within the icmplib/ping.py ping method by:

    except ICMPLibError:
        pass
    

    ICMPLibError: [WinError 10040] A message sent on a datagram socket was larger than the internal message buffer or some other network limit, or the buffer used to receive a datagram into was smaller than the datagram itself

    The flow on effect is that RTTs aren't calculated, and the host.is_alive is incorrectly set to False,

Fix

Simply allocating a buffer that can hold the max possible ICMP packet size (i.e. IPv4 max packet size) seems to work:

response = self._sock.recvfrom(65535)

Example

(Windows), sending 3 pings:

(venv_icmplib) D:\icmplib\examples>python ping.py 
ICMP payload size: 996
host.address: 10.0.0.2
host.min_rtt: 1.0
host.avg_rtt: 1.0
host.max_rtt: 1.279
host.rtts: [1.2786388397216797, 0.9996891021728516, 0.9996891021728516]
host.packets_sent: 3
host.packets_received: 3
host.packet_loss 0.0
host.jitter: 0.139
host.is_alive: True

(venv_icmplib) D:\icmplib\examples>python ping.py
ICMP payload size: 997
host.address: 10.0.0.2
host.min_rtt: 0.0
host.avg_rtt: 0.0
host.max_rtt: 0.0
host.rtts: []
host.packets_sent: 3
host.packets_received: 0
host.packet_loss 1.0
host.jitter: 0.0
host.is_alive: False

This is a nice tool, hope this helps!

Separate parsing from sockets

I have some packets as bytes and want to analyse them. I also want to generate them as bytes. Currently it is done within the classes for sockets, but they are tied to network functions.

It is proposed to move the parsing and serialization code into .models and add the 2 methods, __bytes__ and from_bytes.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.