ValentinBELYN / icmplib

Easily forge ICMP packets and make your own ping and traceroute.
GNU Lesser General Public License v3.0
267 stars 45 forks source link

Support for zone index in IPv6 addresses #34

Closed audeoudh closed 2 years ago

audeoudh commented 3 years ago

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]>
ValentinBELYN commented 3 years ago

Hi @audeoudh 👋

Wow! Thank you for taking the time to detail your request.

I'll see how to implement this for the next release.

sunwire commented 3 years ago

I'll see how to implement this for the next release.

Just replace this line https://github.com/ValentinBELYN/icmplib/blob/40e951c1fb9723a13153034e829afb7b3d0fb13e/icmplib/sockets.py#L272 with this line

self._sock.sendto(packet,socket.getaddrinfo(request.destination, port=None, family=socket.AF_INET if self._IP_VERSION == 4 else socket.AF_INET6, type=socket.SOCK_DGRAM)[0][4])

I've tested it and it looks like everything works

host = ping('fe80::d250:99ff:fe28:7d19%em1' ,count=10, interval=0.2, privileged=False)
(host.address ,host.avg_rtt ,host.is_alive ,host.jitter ,host.max_rtt ,host.min_rtt ,host.packet_loss ,host.packets_received ,host.packets_sent)
('fe80::d250:99ff:fe28:7d19%em1', 0.355, True, 0.17, 0.751, 0.139, 0.0, 10, 10)

host = ping('localhost' ,count=10, interval=0.2, privileged=False)
 (host.address ,host.avg_rtt ,host.is_alive ,host.jitter ,host.max_rtt ,host.min_rtt ,host.packet_loss ,host.packets_received ,host.packets_sent)
('127.0.0.1', 0.462, True, 0.093, 0.604, 0.386, 0.0, 10, 10)
ValentinBELYN commented 3 years ago

Hi @sunwire,

Sorry for the delay.

It is not so simple. I'm trying to avoid putting any code related to IPv6 in the ICMPSocket class. The ideal would be to place this code in the ICMPv6Socket class. However, this class does not have its own send method. I have to find a solution not to duplicate the code.

audeoudh commented 2 years ago

The PR #38 does not add IPv6-specific code to ICMPSocket class. It forwards the self._sock.family and self._sock.type attributes to getaddrinfo. It works for both versions.

audeoudh commented 2 years ago

I added tests to make sure that things works well with these modifications. Do you have a specific test bench to run to ensure all is working well?

ValentinBELYN commented 2 years ago

Hi @audeoudh, sorry for the delay...

Thank you for your PR! I have integrated some of your work in the version 3.0.2 of icmplib. Otherwise, there are no unit tests at the moment, at least on GitHub. I have to improve them before committing them.