ICMP 套接字是两年前 Linux 内核新加入的功能,目的是允许不需要 set-user-id 和CAP_NET_RAW
权限的 ping 程序的实现。大家都知道,set-user-id 程序经常成为本地提权的途径。在 Linux 内核加入此功能之前,以安全为目标的 Openwall GNU/*/Linux 实现了除 ping 程序之外的所有程序去 suid 化……这个功能也是由他们提出并加入的。
我并没有在 man 手册中看到关于 ICMP 套接字的信息。关于 ICMP 套接字使用的细节来自于内核邮件列表。
使用 ICMP 套接字的好处:
- 程序不需要特殊的权限;
- 内核会帮助搞定一些工作。
坏处是:
- 基本没有兼容性可讲;
- 需要调整一个内核参数。
这个内核参数是net.ipv4.ping_group_range
,是一对整数,指定了允许使用 ICMP 套接字的组 ID的范围。默认值为1 0
,意味着没有人能够使用这个特性。手动修改下:
sudo sysctl -w net.ipv4.ping_group_range='0 10'
当然你可以直接去写/proc/sys/net/ipv4/ping_group_range
文件。
如果系统不支持这个特性,在创建套接字的时候会得到「Protocol not supported」错误,而如果没有权限,则会得到「Permission denied」错误。
创建 ICMP 套接字的方法如下:
import socket sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_ICMP)
它的类型和 UDP 套接字一样,是SOCK_DGRAM
,不是SOCK_RAW
哦。这意味着你不会收到 20 字节的 IP 头。不仅仅如此,使用 ICMP 套接字不需要手工计算校验和,因为内核会重新计算的。ICMP id 也是由内核填的。在接收的时候,内核会只把相应 id 的 ICMP 回应返回给程序,不需要自己或者要求内核过滤了。
所以,要组装一个 ICMP ECHO 请求包头很容易了:
header = struct.pack('bbHHh', 8, 0, 0, 0, seq)
这五项依次是:类型(ECHO_REQUEST
)、code(只能为零)、校验和(不需要管)、id(不需要管)、序列号。
接收起来也简单,只要看一下序列号知道是回应自己发的哪个包的就行了。
这里是我的一个很简单的实例。
附注:Mac OS X 在 Linux 之前实现了类似的功能。但是行为可能不太一样。有报告校验和需要自己计算的,也有报告发送正确但是返回报文是乱码的。另,FreeBSD 和 OpenBSD 不支持这个特性。