ValentinBELYN / icmplib

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

Feature: unprivileged traceroute #80

Closed jaraco closed 2 months ago

jaraco commented 4 months ago

Today, I tried to do a traceroute in user space, but got the dreaded permission error:

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

By setting privileged=False in the construction of the _Socket, I was able to perform a traceroute without privilege. Perhaps this project would consiedr adding a privileged parameter to the traceroute function.

jaraco commented 4 months ago

Note, this feature would be unnecessary for my use case if #79 were implemented by adding automatic privilege inference to the _Socket object.

ValentinBELYN commented 2 months ago

Hi @jaraco,

I would like the traceroute function to be able to be run without privileges. However, this is not possible. I provided an answer at the time on this subject: https://github.com/ValentinBELYN/icmplib/issues/6#issuecomment-780099407

If you have a solution, I'm interested!

jaraco commented 2 months ago

However, this is not possible.

I can't remember why at the time I thought that setting privileged=False on the socket would also allow it to run unprivileged.

I went back and tried recreating my findings. I created this Dockerfile:

FROM jaraco/multipy-tox
RUN useradd -ms /bin/bash appuser
USER appuser
WORKDIR src
CMD py -m pip-run . -- -m examples.traceroute

And ran it with docker run -it -v @$(pwd):/src @$(docker build -q .)

It fails with:

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

Patching the project to disable privilege:

diff --git a/icmplib/traceroute.py b/icmplib/traceroute.py
index 7dd45f8..bbaed5f 100644
--- a/icmplib/traceroute.py
+++ b/icmplib/traceroute.py
@@ -167,7 +167,7 @@ def traceroute(address, count=2, interval=0.05, timeout=2, first_hop=1,
     host_reached = False
     hops = []

-    with _Socket(source) as sock:
+    with _Socket(source, privileged=False) as sock:
         while not host_reached and ttl <= max_hops:
             reply = None
             packets_sent = 0

Does allow the program to run, but there are no meaningful hops:

 icmplib main @ docker run -it -v @$(pwd):/src @$(docker build -q .)
[<Hop 2 [1.1.1.1]>]
  *     Some gateways are not responding
  2     1.1.1.1            17.014 ms

So it seems the reason why traceroute requires root but ping doesn't, even though the code is almost identical, is because TTL isn't honored without privilege. Is that right?

ValentinBELYN commented 2 months ago

Yes you are right.

When a router decrements the TTL to 0, the packet is destroyed and the sender is informed by an ICMP Time To Live Exceeded message. The source IP address of this message is not the machine we wish to reach but the router which destroyed the packet.

If we use DGRAM sockets (which is the case if we set the privileged parameter to False), we cannot capture these responses. We can only retrieve responses (therefore ICMP Echo Reply) from the target machine.

Only RAW sockets allow this to be done, unfortunately, but requires privileges on the system.