Ping 与 ICMP 协议

概述

ping 命令是一个非常常用的网络工具,通过 ICMP 协议来探测本地到远端地址之间网络的连通性,以及延迟,稳定等性能指标。但是大多数人其实对 ping 命令的实现了解的并不会太多,因为我们日常的开发工作中,很少会和 ICMP 协议打交道。因为最近在开发 https://github.com/joyme123/gnt,目标是通过单个二进制文件,实现大多数的网络工具的能力。所以接触了一些 ping 命令的实现,这里做一些简单的记录和分享。

ping 命令基于 ICMP 协议的实现

ICMP 协议本身这里不多做介绍,网络上有很多很好很详细的资料。比如维基百科上的这篇介绍:Internet_Control_Message_Protocol

ICMP 和 TCP/UDP 这样的协议有这很大的区别,像 TCP/UDP 这种传输层的协议,都是进程级别的,即可以通过 Port 对应到一个或多个进程。因此在运行使用 TCP/UDP 协议的应用时不需要特殊的权限。而 ICMP 则不一样,它是操作系统级别的,因此早期的 linux 上,如果应用要发送 ICMP 包,则必须通过 socket(AF_INET, SOCK_RAW, int protocol) 这种方式来实现,而调用 SOCK_RAW 则需要 root 权限,或者通过 linux network capability。

因为这种特殊性,后来的 linux 又提供了 socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP) 这种方式去发送 ICMP,这里的 SOCK_DGRAM 是 UDP 协议使用的。不过容易误解的是,并不是说用 UDP 协议去包装或实现了 ICMP 的能力,使用这种方式发送出去的仍然是 ICMP 数据包,不过不再需要 root 权限或者特殊的 network capability 设置了。通常称这种方式为 Unprivileged ICMP。linux 同样也提供了 sysctl 的配置去限制这一能力的使用

# 999~59999 指定了允许的用户组 ID 范围,如果所有用户组都不允许可以设置为 1 0
net.ipv4.ping_group_range = 999 59999

不过需要注意的是,通过 SOCK_DGRAM 只能发送这几种 ICMP 请求:ICMP_ECHO, ICMP_TSTAMP or ICMP_MASKREQ.

解决了如何发送 icmp 包的问题,就可以考虑实现几个主要的 ping 命令特性了。

丢包检测

每个 ICMP 包都有一个 sequence 字段,发送的时候可以指定这个 sequence 的值,目的端响应的时候会把这个 sequence 值设置成一样的,表示响应的是哪一个请求包。这样我们就可以知道每个发送出去的 ICMP 的响应包了,那么没有响应的 sequence 就是被丢弃的包。通过这种方式就可以检测出网络中使用存在丢包现象。

延迟检测

ICMP 支持 echo request 和 reply,即通过 ICMP 协议包装的 payload 发送出去,目的端会原样返回。所以我们可以通过在 request 的时候写入发送时的时间,然后收到回包时取出这个时间就能知道延迟了。

关于写入的时间格式,实现方式上可以随意。但是建议使用 unix time,精确到微秒。然后保留 4 字节的秒+4字节的微秒,写入到 payload 的开头。这样的实现方式和 linux ping 的实现一致,像 wireshark 这种抓包工具就可以识别出来了。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据