Python 非特权 ICMP

29

当我尝试从Python中找出最佳方法来ping(ICMP)一些东西时,我遇到了以下问题:

答案通常归结为“使用带有根权限的第三方模块”或“使用系统的ping命令并解析输出”。在本地方法中,icmplibM. Cowles 和 J. Diemer's ping.py明确提到需要根权限,scapy 手册也是如此。

因此,本地发送ICMP ping而不需要特殊权限似乎是不可能的。系统的ping命令可以管理,但它的手册没有解释其如何实现。另一方面,icmp的手册似乎表明这是可能的:

非特权ICMP
     可以使用SOCK_DGRAM套接字类型打开ICMP套接字,而无需root权限。 概要如下:
socket(AF_INET,SOCK_DGRAM,IPPROTO_ICMP)
面向数据报的ICMP套接字提供了可用功能的一个子集。
能够使用原始ICMP套接字。仅能发送以下类型的ICMP请求消息:ICMP_ECHO、ICMP_TSTAMP或ICMP_MASKREQ。
因此,至少根据icmp,这是被允许的。那么为什么所有的Python工具都无法做到这一点呢?是因为Python工具过于通用,期望对特权套接字的任何操作都需要特权吗?是否可能编写一个可以在没有root特权的情况下执行ping操作的C函数,并使用它来扩展Python?有人这样做了吗?我是否只是误解了问题?

你使用的是什么操作系统?已知最近的Linux内核和Mac OS X具有非特权ICMP套接字。你找到的man页面来自OS X,应该可以工作。我已经成功地使用了一个非特权的纯Python ping程序,它在Linux上工作(需要更改内核设置),但可能需要在OS X上进行一些调整。 - lilydjwg
对于 Linux 系统,请在此处查看 https://dev59.com/pWsy5IYBdhLWcg3w1xaU#20105379,你需要一个特殊的 sysctl 来使用 socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP) - nos
9个回答

15

ping程序被安装为setuid root。这使得任何用户都可以使用该程序,并且仍然能够打开原始套接字。

在它打开原始套接字之后,通常会放弃root权限。

通常需要原始套接字才能正确执行ICMP操作,并且原始套接字通常受到限制。因此,这并不是Python的错。

关于上面提到的ICMP位的问题,显然许多实现并不真正支持这些标志的组合。因此,大多数实现只使用它们“知道”的在大多数/所有架构上都有效的方式。


啊,这就少了一个谜团 - 因为在 ping 上使用短时间间隔仍然需要 sudo,我认为它没有设置 setuid root,但你显然是正确的。 - Markus
Ping并不一定使用ICMP探测,也不需要原始套接字或root权限。 - jean-loup

14

以下是 /sbin/ping 在大多数 Unix 系统上是如何“某种方式”管理的:

$ ls -l /sbin/ping
-r-sr-xr-x  1 root  wheel  68448 Jan 26 10:00 /sbin/ping

看到了吗?它是由 root 拥有并拥有关键的权限标识符 s,即设置用户ID。因此,无论哪个用户运行它,ping 都将以 root 身份运行。

如果您正在使用具有新的“非特权ICMP套接字”的BSD内核,则有趣的是看看如何使用该功能从Python中进行ping(但这当然不能帮助任何运行较旧内核的用户)。


1
有趣,非特权ICMP套接字是新的吗?也许我会在有时间的时候尝试用Python实现它,主要是出于好奇。 - Markus
1
并不是新规范(我相信它可以追溯到古老的BSD 4.3!-),但实现这个规范的工作会相当新(而且在我看来非常好)。 - Alex Martelli
1
我的 Arch Linux 中的 ping 没有那个位。 - FelipeC

6

我不确定在一个问题中发布一些早先已经被回答的东西是否可以。

我一直在寻找同样的实现方式,并找到了一种使用Python进行ICMP的非root权限的方法。

python-ping使用相同的“需要root”方式来执行ping,但遇到了一个错误报告,其中一个用户建议在调用sock时将SOCK_RAW更改为SOCK_DGRAM

http://hg.io/delroth/python-ping/issue/1/icmp-without-root-privilege

开发人员解释说,这将是一个“WONT-FIX”情况,因为它是一个UDP ping。

由于我真的不在乎ICMP是否通过UDP发出,所以我继续获取了代码并进行了建议的更改。

现在,我能够进行ping而无需调用子进程或需要root!

再次强调,我不确定在这么长时间之后在此处发布是否可以,但我认为这是一个更好的事情!


1
这是一个有趣的方法。应该可以很好地工作,但严格来讲,这不再是一个ping了。这意味着具有特殊ping处理功能的防火墙等可能会表现出不同的行为。 - DonGar
3
那种方法根本行不通。你无法使用 ICMP 协议创建 UDP 类型的套接字。我得到了 EPROTONOSUPPORT(协议不支持)错误。原因很简单:这样的协议组合不存在。(可以使用 UDP/TCP 在非特权状态下进行响应测试,但那需要比这更复杂的变化。) - Robert Siemer

5

现代的Linux平台使用libcap实现ping命令,并使用libcap管理权限,通过检查(capget/set函数)和管理权限来完成这一过程:

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)   

假设您有一个“myping”程序:
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  

现在执行:
linux@jacax:~/WORK$ getcap ./myping  
./ping = cap_net_raw+ep

现在,您的“myping”将可以无需root运行。也就是说,只要“myping”实际上是一个二进制程序即可。如果它是一个脚本,则必须在脚本解释器上设置此功能。

1
非常好的观点,我几乎绝望了,因为没有人提到能力。然而,必须注意的是,如果你的“程序”是一个脚本,能力必须在脚本解释器中设置,而不是在脚本中设置。 - 0xC0000022L

4

这对我很有效: result = ping("1.1.1.1", privileged=False) - Simon

2

实际上,在Windows 7和Vista上,你需要“以管理员身份运行”才能执行以下操作:

my_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, icmp)

正如你所指出的,使用数据报套接字进行操作会导致错误。


0
您正在阅读的手册是关于“BSD内核接口手册”,看起来来自“Mac OS X 10.9”。我没有Mac OS X机器可以尝试,但在Linux下,作为root或用户,当我尝试打开这样的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

在OpenBSD下我遇到了“协议不支持”的错误:
>>> 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

也许有人可以在MacOS X或其他BSD上尝试,但无论如何,这种套接字类型看起来都不太可移植!

你做错了,ICMP消息应该通过DGRAM套接字作为错误消息获取。在DGRAM上指定ICMP作为套接字协议是错误的。 - jean-loup

-1
我也在寻找一个不使用子进程或需要root来ping的实现。我的解决方案需要跨平台,即Windows和Linux。
将Windows上的套接字更改为SOCK_DGRAM会导致“协议不支持100043”异常。因此,看起来Windows正确地检查了是否正在通过TCP而不是UDP发送icmp。但是,Windows并不关心它是否作为“root”运行,因为那是Linux的概念。
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)

-9

我正在Windows 7下运行Python,由于我正在使用Eclipse PyDev插件编辑和“编译”代码,我的解决方案是:以管理员身份运行eclipse.exe:这解决了问题。

这个解决方案类似于以管理员身份运行cmd。


10
请勿建议人们以管理员身份运行任意程序。这正是用户账户控制(UAC)首次遭到指责的原因。您的回答也不适用于部署到生产环境中的程序。 - AdmiralNemo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接