bigywater.tech
网络协议 · 22

ICMP 限速与重定向:系统为什么不回你每一个包

为什么对一个关闭的 UDP 端口狂发,只能收到零星几个“端口不可达”?traceroute 里的星号一定是路径有问题吗?这篇直接测量本机和网关回 ICMP 差错的速度,对比 echo 应答、TCP RST 和被禁用的时间戳 / 掩码请求。

每 100 ms 一个
本机回 ICMP 端口不可达的上限:发 374,840 个包只回了 31 个
3000 / 3000
同样猛发时 ICMP echo 应答全部回应,没有限速;TCP RST 也是 1500 / 1500
4 / 100
对默认网关的关闭端口 1 ms 间隔发 100 个 UDP,收到的端口不可达个数

内核开关

实测 · 本机执行输出
  net.inet.icmp.maskrepl: 0
  net.inet.icmp.timestamp: 0
  net.inet.icmp.drop_redirect: 1
  net.inet.icmp.log_redirect: 0
  net.inet.icmp.bmcastecho: 1
  net.inet.icmp.suppress_icmp_port_unreach: 0
  net.inet.icmp.fraglimit: 16
  net.inet.icmp.fragtimeout: 5
  net.inet.ip.redirect: 1
  net.inet.ip.forwarding: 0
  net.inet.icmp 下没有 icmplim 这一项:macOS 不像 Linux / BSD 那样用 sysctl 设 ICMP 限速;下面一节直接测量
  netstat -s -p icmp 的 Input histogram 里没有“redirect”一项;Output histogram 里的类别:echo reply,destination unreachable

net.inet.icmp 下有 maskrepl、timestamp、drop_redirect、bmcastecho 等开关,但没有 ICMP 限速的 sysctl。

ICMP 差错限速

实测 · 本机执行输出
  间隔      0 ms:3 秒内发了 374840 个 UDP 包,抓到 31 个 ICMP 差错(10.3 个/秒);相邻差错的间隔 最小 99 / 中位 100 / 最大 100 ms;netstat 里 icmp_error 调用 +31,“destination unreachable”输出 +31
  间隔      1 ms:3 秒内发了   2961 个 UDP 包,抓到 30 个 ICMP 差错(10.0 个/秒);相邻差错的间隔 最小 99 / 中位 100 / 最大 100 ms;netstat 里 icmp_error 调用 +30,“destination unreachable”输出 +30
  间隔      5 ms:3 秒内发了    598 个 UDP 包,抓到 30 个 ICMP 差错(10.0 个/秒);相邻差错的间隔 最小 100 / 中位 100 / 最大 101 ms;netstat 里 icmp_error 调用 +30,“destination unreachable”输出 +30
  间隔     10 ms:3 秒内发了    300 个 UDP 包,抓到 30 个 ICMP 差错(10.0 个/秒);相邻差错的间隔 最小 100 / 中位 100 / 最大 101 ms;netstat 里 icmp_error 调用 +30,“destination unreachable”输出 +30
  间隔     50 ms:3 秒内发了     60 个 UDP 包,抓到 30 个 ICMP 差错(10.0 个/秒);相邻差错的间隔 最小 100 / 中位 100 / 最大 100 ms;netstat 里 icmp_error 调用 +30,“destination unreachable”输出 +30
  间隔    200 ms:3 秒内发了     15 个 UDP 包,抓到 15 个 ICMP 差错(5.0 个/秒);相邻差错的间隔 最小 200 / 中位 200 / 最大 200 ms;netstat 里 icmp_error 调用 +15,“destination unreachable”输出 +15
  间隔   1000 ms:5 秒内发了      5 个 UDP 包,抓到  5 个 ICMP 差错(1.0 个/秒);相邻差错的间隔 最小 1000 / 中位 1000 / 最大 1000 ms;netstat 里 icmp_error 调用 +5,“destination unreachable”输出 +5
  结论:不管发多快,最多约每 100 ms 回一个差错(≈ 10 个/秒);发得慢于这个速度时每个都回

本机生成 ICMP 差错的速度上限约为每 100 ms 一个,发包再快也不增加;发得慢于这个速度时每个都回。限掉的包没有走到生成 ICMP 的步骤,netstat 的计数也不增加。

限的是什么

实测 · 本机执行输出
  ICMP echo:发出 3000 个请求(抓到 3000 个请求),收到 3000 个应答——echo 应答没有被限成 10 个/秒
  TCP:对没人监听的端口连续发起 1500 次 connect(0.07 秒),抓到 1500 个 SYN,回了 1500 个 RST——TCP 的拒绝(RST)在这里没有被限成 10 个/秒
  UDP 端口不可达(ICMP):3 秒内发了 374840 个,只回了 31 个

限速针对 ICMP 差错报文(这里测的是端口不可达);echo 应答和 TCP 的 RST 没有被限制。

路由器

实测 · 本机执行输出
  端口不可达(发到网关自己的关闭端口):103 ms 内发了 100 个,收到 4 个差错;相邻间隔 最小 0 / 中位 0 / 最大 2 ms
  超时(TTL=1,网关要转发时发现 TTL 用完):103 ms 内发了 100 个,收到 3 个差错;相邻间隔 最小 2 / 中位 2 / 最大 2 ms
  设备各有各的策略(有的按速率、有的按令牌桶);这里只看到这台网关回应的个数,没有看到它的配置

默认网关对端口不可达和超时也只回几个,回应集中在最初几毫秒。traceroute 里的星号和只回 1/3 的跳,有一部分只是路由器在限速。

被关掉的 ICMP

实测 · 本机执行输出
  对 127.0.0.1 发 时间戳请求(类型 13):1 秒内从套接字收到 1 个 ICMP 包,类型 [13]——应答类型是 14,没有收到
  对 127.0.0.1 发 地址掩码请求(类型 17):1 秒内从套接字收到 1 个 ICMP 包,类型 [17]——应答类型是 18,没有收到
  对 127.0.0.1 发 回显请求(类型 8,对照):1 秒内从套接字收到 1 个 ICMP 包,类型 [0]——应答类型是 0,收到了

timestamp = 0、maskrepl = 0 时,本机不回时间戳请求和地址掩码请求;redirect 方面本机丢弃收到的重定向(drop_redirect = 1)且不转发(ip.forwarding = 0)。

分步演示

ICMP 限速
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64(Apple 芯片)/ Python 3.14.8 / tcpdump(libpcap)/ scapy 2.8 / dpkt / dnspython / h2 / aioquic / OpenSSL / curl 8.7.1;抓包在 lo0(环回)上做,少数实验用真实网卡 en0(这台机器上 en0 是 Wi-Fi,tcpdump 看到的是系统转成以太网格式的帧,所以文中的“以太网头”都指这个 14 字节的头)或访问公网(会在文章里注明);需要丢包 / 延迟 / 限速时,用一个只作用于 lo0 端口 9400-9499 的 dummynet 辅助脚本(netlab);所有服务端都是本机自己起的进程;时间戳取自 tcpdump 的 -tt,数字每次运行会有小幅抖动。第二、三节在 lo0 上发包并抓包(次数很大,只看个数与间隔);第四节对默认网关各发 100 个小包(约 0.1 秒),没有造成持续负载;第五节用非特权 ICMP 套接字发请求,只对 127.0.0.1
    • 没有验证:其它 ICMP 差错类型(超时、需要分片)在本机是否同样限速;限速的内核实现与是否可调(net.inet.icmp 下没有对应项);网关的限速算法;ICMP 重定向的实际接收和处理(没有能制造重定向的环境);IPv6 的 ICMPv6 限速;不同 macOS 版本的差异
    京ICP备2026067502号-1京公网安备11010502063487号