python non-privileged ICMP
Question:
While trying to figure out the best method to ping (ICMP) something from python, I came across these questions:
- How can I perform a ping or traceroute in python, accessing the output as it is produced?
- ping a site in python
- How can I perform a ping or traceroute using native python?
The answers generally boil down to “use this third party module with root privileges” or “use the system’s ping command and parse the output”. Of the native methods, icmplib and M. Cowles and J. Diemer’s ping.py explicitly mention the need for root privileges, as does the scapy manual.
So from that front, natively sending ICMP pings without special privileges seems impossible. The system ping command does manage somehow, but its man page doesn’t shed any light on how. The man page for icmp, on the other hand, seems to say it’s possible:
Non-privileged ICMP
ICMP sockets can be opened with the SOCK_DGRAM socket type without
requiring root privileges. The synopsis is the following:
socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP)
Datagram oriented ICMP sockets offer a subset of the functionality avail-
able to raw ICMP sockets. Only IMCP request messages of the following
types can be sent: ICMP_ECHO, ICMP_TSTAMP or ICMP_MASKREQ.
So it would seem that, at least according to icmp, it’s allowed. So why is it that all the python tools are unable to do this? Are the python tools too general and expect any work on privileged sockets to be privileged? Would it be possible to write a ping function in C that can ping without root privileges, and extend python with this? Has anyone done this? Have I just misunderstood the problem?
Answers:
The ping program is installed setuid root. This allows any user to use the program, and still be able to open a raw socket.
After it opens the raw socket, it typically drops root privs.
You generally need a raw socket to do ICMP correctly, and raw sockets are usually restricted. So it’s not really python’s fault at all.
Regarding the bit about ICMP above, apparently many implementations don’t really support those combinations of flags well. So it is likely that most implmentations just use the way they “know” works on most / all architectures.
Here’s how /sbin/ping “somehow manages” (on most Unix-y systems):
$ ls -l /sbin/ping
-r-sr-xr-x 1 root wheel 68448 Jan 26 10:00 /sbin/ping
See? It’s owned by root
and has that crucial s
bit in the permission — setuserid. So, no matter what user is running it, ping runs as root.
If you’re using a BSD Kernel with the new “non-privileged ICMP sockets” it would be interesting to see what’s needed to use that functionality to ping from Python (but that won’t help any user that’s on a less advanced kernel, of course).
I’m not sure if it is OK to post something in a question that seems it has already been answered a while ago.
I have been searching for the same implementation and found a way to do ICMP via Python with non-root privileges.
python-ping
uses the same ‘need-root’ way to do a ping, but came across a bug report where a user suggested changing SOCK_RAW
to SOCK_DGRAM
when calling sock
:
http://hg.io/delroth/python-ping/issue/1/icmp-without-root-privilege
The dev explains this will be a “WONT-FIX” situation because it is a UDP ping rather.
Since I really do not care if ICMP is going out via UDP, I went ahead and got the code and made the proposed changed.
I am now able to do a ping without calling subprocess or needing root!
Again, not sure if posting here after such a long time is OK, but thought this was a better thing!
I was also looking for an implementation of ping without using subprocess or needing root to ping. My solution needed to be cross-platform, namely Windows and Linux.
Changing the socket on Windows to SOCK_DGRAM results in a “protocol not supported 100043” exception. So it looks like Windows correctly checks to see if icmp is being sent out on TCP rather than UDP. However, windows does not care if it is running as “root” since that is a Linux concept.
if os.name == 'nt':
#no root on windows
my_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, icmp)
else:
#changed to UDP socket...gets around ROOT priv issue
my_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, icmp)
Actually, on Windows 7 and Vista you do need to ‘Run as Administrator’ to do:
my_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, icmp)
and as you note, doing it over a datagram socket causes an error.
I am running python under windows 7 ,
Since i am editing and “compiling” the code under Eclipse pydev plugin,
My solution was : Running the eclipse.exe as an administrator : this solved the problem,
This solution is similar to running the cmd as an administrator.
The man page you’re reading is about “BSD Kernel Interfaces Manual” and seems to come from “Mac OS X 10.9”. I don’t have a Mac OS X machine to try, but under Linux, as root or as user I get a permission denied error when I try to open such an ICMP:
$ strace -e trace=socket python
Python 2.7.5+ (default, Sep 19 2013, 13:48:49)
[GCC 4.8.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import socket
>>> socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_ICMP)
socket(PF_INET, SOCK_DGRAM, IPPROTO_ICMP) = -1 EACCES (Permission denied)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python2.7/socket.py", line 187, in __init__
_sock = _realsocket(family, type, proto)
socket.error: [Errno 13] Permission denied
Under OpenBSD I get a “Protocol not supported” error:
>>> import socket
>>> socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_ICMP)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib/python2.7/socket.py", line 187, in __init__
_sock = _realsocket(family, type, proto)
socket.error: [Errno 43] Protocol not supported
May be someone could try under MacOS X or other BSDs, but anyway this socket type does not look like portable, to say the least!
Modern Linuxes ping uses libcap and asks libcap to do the work.This checks (capget/set funcitons) and manage permissions:
linux@jacax:~/WORK$ ldd /bin/ping
linux-gate.so.1 => (0xb77b6000)
libcap.so.2 => /lib/i386-linux-gnu/libcap.so.2 (0xb7796000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb75e7000)
/lib/ld-linux.so.2 (0xb77b7000)
Lets say you have a “myping” program:
linux@jacax:~/WORK$ getcap ./myping
linux@jacax:~/WORK$ (-> nothing! )
linux@jacax:~/WORK$ setcap cap_net_raw=ep ./myping
unable to set CAP_SETFCAP effective capability: Operation not permitted
linux@jacax:~/WORK$ sudo setcap cap_net_raw=ep ./myping
Now do:
linux@jacax:~/WORK$ getcap ./myping
./ping = cap_net_raw+ep
Now, your “myping” will work without root. That is, as long as myping
is in fact a binary program. If it is a script, this capability has to be set on the script interpreter instead.
icmplib module helped me with ping without running whole django app as root:
https://pypi.org/project/icmplib/
While trying to figure out the best method to ping (ICMP) something from python, I came across these questions:
- How can I perform a ping or traceroute in python, accessing the output as it is produced?
- ping a site in python
- How can I perform a ping or traceroute using native python?
The answers generally boil down to “use this third party module with root privileges” or “use the system’s ping command and parse the output”. Of the native methods, icmplib and M. Cowles and J. Diemer’s ping.py explicitly mention the need for root privileges, as does the scapy manual.
So from that front, natively sending ICMP pings without special privileges seems impossible. The system ping command does manage somehow, but its man page doesn’t shed any light on how. The man page for icmp, on the other hand, seems to say it’s possible:
Non-privileged ICMP ICMP sockets can be opened with the SOCK_DGRAM socket type without requiring root privileges. The synopsis is the following: socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP) Datagram oriented ICMP sockets offer a subset of the functionality avail- able to raw ICMP sockets. Only IMCP request messages of the following types can be sent: ICMP_ECHO, ICMP_TSTAMP or ICMP_MASKREQ.
So it would seem that, at least according to icmp, it’s allowed. So why is it that all the python tools are unable to do this? Are the python tools too general and expect any work on privileged sockets to be privileged? Would it be possible to write a ping function in C that can ping without root privileges, and extend python with this? Has anyone done this? Have I just misunderstood the problem?
The ping program is installed setuid root. This allows any user to use the program, and still be able to open a raw socket.
After it opens the raw socket, it typically drops root privs.
You generally need a raw socket to do ICMP correctly, and raw sockets are usually restricted. So it’s not really python’s fault at all.
Regarding the bit about ICMP above, apparently many implementations don’t really support those combinations of flags well. So it is likely that most implmentations just use the way they “know” works on most / all architectures.
Here’s how /sbin/ping “somehow manages” (on most Unix-y systems):
$ ls -l /sbin/ping
-r-sr-xr-x 1 root wheel 68448 Jan 26 10:00 /sbin/ping
See? It’s owned by root
and has that crucial s
bit in the permission — setuserid. So, no matter what user is running it, ping runs as root.
If you’re using a BSD Kernel with the new “non-privileged ICMP sockets” it would be interesting to see what’s needed to use that functionality to ping from Python (but that won’t help any user that’s on a less advanced kernel, of course).
I’m not sure if it is OK to post something in a question that seems it has already been answered a while ago.
I have been searching for the same implementation and found a way to do ICMP via Python with non-root privileges.
python-ping
uses the same ‘need-root’ way to do a ping, but came across a bug report where a user suggested changing SOCK_RAW
to SOCK_DGRAM
when calling sock
:
http://hg.io/delroth/python-ping/issue/1/icmp-without-root-privilege
The dev explains this will be a “WONT-FIX” situation because it is a UDP ping rather.
Since I really do not care if ICMP is going out via UDP, I went ahead and got the code and made the proposed changed.
I am now able to do a ping without calling subprocess or needing root!
Again, not sure if posting here after such a long time is OK, but thought this was a better thing!
I was also looking for an implementation of ping without using subprocess or needing root to ping. My solution needed to be cross-platform, namely Windows and Linux.
Changing the socket on Windows to SOCK_DGRAM results in a “protocol not supported 100043” exception. So it looks like Windows correctly checks to see if icmp is being sent out on TCP rather than UDP. However, windows does not care if it is running as “root” since that is a Linux concept.
if os.name == 'nt':
#no root on windows
my_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, icmp)
else:
#changed to UDP socket...gets around ROOT priv issue
my_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, icmp)
Actually, on Windows 7 and Vista you do need to ‘Run as Administrator’ to do:
my_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, icmp)
and as you note, doing it over a datagram socket causes an error.
I am running python under windows 7 ,
Since i am editing and “compiling” the code under Eclipse pydev plugin,
My solution was : Running the eclipse.exe as an administrator : this solved the problem,
This solution is similar to running the cmd as an administrator.
The man page you’re reading is about “BSD Kernel Interfaces Manual” and seems to come from “Mac OS X 10.9”. I don’t have a Mac OS X machine to try, but under Linux, as root or as user I get a permission denied error when I try to open such an ICMP:
$ strace -e trace=socket python
Python 2.7.5+ (default, Sep 19 2013, 13:48:49)
[GCC 4.8.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import socket
>>> socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_ICMP)
socket(PF_INET, SOCK_DGRAM, IPPROTO_ICMP) = -1 EACCES (Permission denied)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python2.7/socket.py", line 187, in __init__
_sock = _realsocket(family, type, proto)
socket.error: [Errno 13] Permission denied
Under OpenBSD I get a “Protocol not supported” error:
>>> import socket
>>> socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_ICMP)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib/python2.7/socket.py", line 187, in __init__
_sock = _realsocket(family, type, proto)
socket.error: [Errno 43] Protocol not supported
May be someone could try under MacOS X or other BSDs, but anyway this socket type does not look like portable, to say the least!
Modern Linuxes ping uses libcap and asks libcap to do the work.This checks (capget/set funcitons) and manage permissions:
linux@jacax:~/WORK$ ldd /bin/ping
linux-gate.so.1 => (0xb77b6000)
libcap.so.2 => /lib/i386-linux-gnu/libcap.so.2 (0xb7796000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb75e7000)
/lib/ld-linux.so.2 (0xb77b7000)
Lets say you have a “myping” program:
linux@jacax:~/WORK$ getcap ./myping
linux@jacax:~/WORK$ (-> nothing! )
linux@jacax:~/WORK$ setcap cap_net_raw=ep ./myping
unable to set CAP_SETFCAP effective capability: Operation not permitted
linux@jacax:~/WORK$ sudo setcap cap_net_raw=ep ./myping
Now do:
linux@jacax:~/WORK$ getcap ./myping
./ping = cap_net_raw+ep
Now, your “myping” will work without root. That is, as long as myping
is in fact a binary program. If it is a script, this capability has to be set on the script interpreter instead.
icmplib module helped me with ping without running whole django app as root:
https://pypi.org/project/icmplib/