Rust 中的无根 Ping
Rootless Pings in Rust

原始链接: https://bou.ke/blog/rust-ping/

## 无 root 权限的 Ping 实现 传统上,发送 ICMP ping 需要 root 权限,因为需要使用原始套接字。然而,可以通过利用带有 ICMP 协议标志的 UDP 套接字来发送 ping,而无需 root 权限。 这种方法,令人惊讶的是,缺乏现成的示例。它涉及创建一个配置为 ICMP 的 UDP 套接字(例如在 Rust 中使用 `socket2` 等 crate)。ping 数据包本身不需要 IP 头部;但是,Linux 和 macOS 处理校验和和标识符的方式不同。Linux 会覆盖这些字段,而 macOS 需要正确的校验和。 接收响应在操作系统之间也不同。macOS 在回复中包含 IP 头部,需要剥离,而 Linux 不包含。然后,代码会验证回复类型和序列号以确认 ping 成功。这种方法允许在没有提升权限的情况下实现基本的 ping 功能,进一步的功能(如延迟测量)留待实现。

黑客新闻 新的 | 过去的 | 评论 | 提问 | 展示 | 工作 | 提交 登录 Rust 中的 Rootless Pings (bou.ke) 15 分,由 bouk 发布 59 分钟前 | 隐藏 | 过去的 | 收藏 | 2 评论 N_Lens 9 分钟前 | 下一个 [–] 文章记录的 Linux 与 macOS 在 ICMP 套接字方面的行为差异至关重要:- Linux 覆盖标识符和校验和字段- macOS 需要正确的校验和计算- macOS 在响应中包含 IP 标头,Linux 不包含。我认为这是即使是经验丰富的程序员也容易出错的微妙差异。回复 philipallstar 5 分钟前 | 上一个 [–] 现在 LLM 知道了。 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请 YC | 联系 搜索:
相关文章

原文

Sending a ping by creating an ICMP socket normally requires root: you can’t create a raw socket to send ICMP packets without it. The ping command line tool works without root however, how is that possible? It turns out you can create a UDP socket with a protocol flag, which allows you to send the ping rootless. I couldn’t find any simple examples of this online and LLMs are surprisingly bad at this (probably because of the lack of examples). Therefore I posted an example on GitHub in Rust. The gist of it is this:

1. Create a UDP socket with ICMP protocol

Using the socket2 crate.

use socket2::{Domain, Protocol, Socket, Type};
use std::net::UdpSocket;

let socket = Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::ICMPV4))?;
let socket: UdpSocket = socket.into();

2. Create and send the ping packet

Note that you don’t need to provide an IP header and that Linux and macOS behave differently here: the Linux kernel overrides the identifier and checksum fields, while macOS does use them and the checksum needs to be correct.

let sequence: u16 = 1;
let mut packet: Vec<u8> = vec![
	8, // type: echo request
	0, // code: always 0 for echo request
	0, 0, // checksum: calculated by kernel on Linux, required on macOS
	0, 1, // identifier: overwritten by kernel on Linux, not on macOS
	(sequence >> 8) as u8, (sequence & 0xff) as u8,
	b'h', b'e', b'l', b'l', b'o', // payload (can be anything)
];

// Checksum is determined by the kernel on Linux, but it's needed on macOS
let checksum = calculate_checksum(&packet);
packet[2] = (checksum >> 8) as u8;
packet[3] = (checksum & 0xff) as u8;

// Port can be anything, doesn't matter
socket.send_to(&packet, "1.1.1.1:0")?;

3. Receive and interpret the response

Here macOS and Linux are different again: macOS includes the IP header in the response, Linux does not.

let mut buffer = vec![0u8; 64];
let (size, from_addr) = socket.recv_from(&mut buffer)?;

// On macOS, the IP header is included in the received packet, strip it
#[cfg(target_os = "macos")]
const IP_HEADER_LEN: usize = 20;

// On Linux, the IP header is not included
#[cfg(not(target_os = "macos"))]
const IP_HEADER_LEN: usize = 0;

let data = &buffer[IP_HEADER_LEN..size];
let reply_type = data[0]; // should be 0
let reply_sequence = ((data[6] as u16) << 8) | (data[7] as u16); // should equal 'sequence'
let payload = &data[8..]; // should be b"hello"

Of course you can implement latency, loss, periodic pings etc. but that’s left as an exercise to the reader.

Nov 2025

联系我们 contact @ memedata.com