2652 字
13 分钟
Verizon Fios 的隐形 MAC 封锁:ICMP Redirect 触发 IPv4 全断

openclaw-gateway 的 Telegram 和微信通道同时挂了。日志里全是 TypeError: fetch failedUND_ERR_CONNECT_TIMEOUT,每隔 30 秒循环一次。两个完全不同的 API 端点同时挂,第一反应就是网络层出了问题。

症状#

gateway 跑在一台 Intel MacBook Pro 上(以下简称 mbp),Node.js 25.5.0。错误日志长这样:

[openclaw-weixin] weixin getUpdates error (3/3): TypeError: fetch failed
[openclaw-weixin] weixin getUpdates: 3 consecutive failures, backing off 30s
[telegram] fetch fallback: enabling sticky IPv4-only dispatcher (codes=UND_ERR_CONNECT_TIMEOUT)
[telegram] fetch fallback: DNS-resolved IP unreachable; trying alternative Telegram API IP

Telegram 的错误更详细——能看到 undici 在尝试各种 fallback:先切 IPv4-only,再试备用 IP,全部超时。微信那边更简单粗暴,直接 fetch failed

第一轮排查:IPv4 vs IPv6#

先用 curl 分别测 IPv4 和 IPv6:

Terminal window
curl -4 -sv --connect-timeout 10 https://api.telegram.org 2>&1 | head -5
* Trying 149.154.166.110:443...
* Connected to api.telegram.org (149.154.166.110) port 443
* SSL connection timeout

IPv4:TCP 能连上,但 TLS 握手超时。ClientHello 发出去了,服务器不回 ServerHello。

Terminal window
curl -6 -sv --connect-timeout 10 https://api.telegram.org 2>&1 | head -5
* Trying [2001:67c:4e8:f004::9]:443...
* Connected to api.telegram.org (2001:67c:4e8:f004::9) port 443
* (304) (IN), TLS handshake, Server hello (2):

IPv6:秒通。

再验证 Node.js:

Terminal window
node -e "
const dns = require('dns');
dns.setDefaultResultOrder('verbatim');
fetch('https://api.telegram.org').then(r => console.log('OK', r.status))
.catch(e => console.log('FAIL', e.message));
"
OK 200

把 DNS 结果顺序设为 verbatim(系统返回什么顺序就用什么),Node.js 就能连了。因为系统 DNS 返回 IPv6 在前,verbatim 保持了这个顺序,所以走了 IPv6。

到这里看起来像是 Node 25.x 的 undici 双栈处理 bug——默认先尝试 IPv4,卡在那里不回退。但这只是表象,真正的问题是:为什么 IPv4 不通?

第二轮:这不是某个站的问题#

测更多站点:

Terminal window
curl -4 --noproxy '*' -s --connect-timeout 5 -o /dev/null -w '%{http_code}' https://www.google.com
# 000
curl -4 --noproxy '*' -s --connect-timeout 5 -o /dev/null -w '%{http_code}' https://www.apple.com
# 000
curl -4 --noproxy '*' -s --connect-timeout 5 -o /dev/null -w '%{http_code}' https://www.cloudflare.com
# 000

所有 IPv4 HTTPS 都不通。 不只是 Telegram 和微信。

再测 ping:

Terminal window
ping -c 2 8.8.8.8
# 64 bytes from 8.8.8.8: icmp_seq=0 ttl=119 time=18.019 ms

Ping 正常。所以 ICMP 通、IPv6 通、IPv4 TCP connect 能完成三次握手、但数据传输被吞。

第三轮:同一 LAN,不同设备#

同一局域网的 Mac Mini(以下简称 mini):

Terminal window
# 从 mini 测试
curl -4 -s --connect-timeout 5 https://www.google.com -o /dev/null -w '%{http_code}'
# 200

mini 完全正常。同一个路由器、同一条宽带,mbp 不通 mini 通。

再加上一台 Raspberry Pi(以下简称 rp),它通过 OpenWrt 做 AP 桥接回 Fios 路由器:

Terminal window
# 从 rp 测试
curl -4 -s --connect-timeout 5 https://www.google.com -o /dev/null -w '%{http_code}'
# 000

rp 也不通。两台不通,一台通。

第四轮:发现 Tailscale 配置异常#

三台设备都跑了 Tailscale。对比配置:

Terminal window
tailscale debug prefs | grep -E "RouteAll|AdvertiseRoutes|ExitNodeID"
设备RouteAllAdvertiseRoutesExitNodeID
mbp (不通)true['0.0.0.0/0', '::/0']""
rp (不通)true['0.0.0.0/0', '::/0']""
mini (正常)truenull""

关键区别:mbp 和 rp 都在广播自己为 exit node(AdvertiseRoutes: 0.0.0.0/0),同时 RouteAll: trueExitNodeID 为空。

这是 Tailscale GitHub #18923 记录的一个已知 bug:macOS GUI 关闭 exit node 后没有清除 RouteAll 标志。RouteAll: true + 空 ExitNodeID = Tailscale 会安装 blackhole 路由,把 IPv4 流量吞掉。

修复方式:

Terminal window
tailscale up --exit-node="" --reset

注意必须用 --reset,单独 tailscale set --exit-node= 不会清除 RouteAll

第五轮:修了 Tailscale,但问题没解决#

在 rp 上执行修复:

Terminal window
sudo tailscale up --exit-node="" --reset
tailscale debug prefs | grep RouteAll
# "RouteAll": false,

配置确认清除了。重启 tailscaled:

Terminal window
sudo systemctl restart tailscaled

再测:

Terminal window
curl -4 -s --connect-timeout 8 -o /dev/null -w '%{http_code}' https://www.google.com
# 000

还是不通。

更激进——直接停掉 Tailscale:

Terminal window
sudo systemctl stop tailscaled
systemctl is-active tailscaled
# inactive

检查 iptables:

Chain INPUT (policy ACCEPT)
Chain FORWARD (policy ACCEPT)
Chain OUTPUT (policy ACCEPT)

干干净净,只有 Docker 的默认规则。没有 Tailscale 残留。

再测:

Terminal window
curl -4 -s --max-time 5 -o /dev/null -w '%{http_code}' http://httpbin.org/ip
# 000

Tailscale 完全停了,iptables 干净,IPv4 仍然不通。

第六轮:排除 Fios 路由器#

通过 SSH tunnel 连进 Fios 路由器管理界面,查看所有日志:

  • Security Log:只有 Web 登录记录
  • Firewall Log:只有入站拦截(外部 ICMPv6 探测被拒),零出站阻断
  • Access Control:空的,没有规则
  • Advanced Log:WiFi association/steering 事件

Fios 没有封锁任何出站流量。

重启 Fios 路由器后再测——mbp 和 rp 仍然不通,mini 仍然正常。

第七轮:用 strace 看清真相#

在 rp 上用 strace 抓 curl 的系统调用:

Terminal window
sudo strace -e trace=network curl -4 --noproxy '*' -sv --max-time 5 http://example.com 2>&1
connect(5, {sa_family=AF_INET, sin_port=htons(80),
sin_addr=inet_addr("104.18.26.120")}, 16) = -1 EINPROGRESS
getsockopt(5, SOL_SOCKET, SO_ERROR, [0], [4]) = 0
sendto(5, "GET / HTTP/1.1\r\nHost: example.co"..., 75, MSG_NOSIGNAL, NULL, 0) = 75
# ... 然后什么都没有,5 秒后超时

TCP 三次握手完成(connect 成功),HTTP 请求 75 字节成功写入 socket(sendto 返回 75),然后……内核从未收到任何响应数据

用 Python raw socket 在 mini 和 rp 上做对比:

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(5)
s.connect(("104.18.26.120", 80))
s.send(b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
data = s.recv(200)
print("RECV:", data[:100])

mini:

RECV: b'HTTP/1.1 200 OK\r\nDate: Tue, 31 Mar 2026 21:49:39 GMT...'

rp:

TimeoutError: timed out

同样的代码、同样的目标 IP、同一个路由器出去——mini 收到响应,rp 什么都收不到。

第八轮:关掉 mbp 的 Tailscale#

mbp 的 SSH 走的是 Tailscale,直接 tailscale down 会断开连接。用 ProxyJump 绕过:

Terminal window
# 从 mini 跳到 mbp 的 LAN IP
ssh -o ProxyJump=mini lishuyu@192.168.1.183 \
"tailscale down --accept-risk=lose-ssh"

然后测试:

Terminal window
ssh -o ProxyJump=mini lishuyu@192.168.1.183 \
"curl -4 --noproxy '*' -s --max-time 8 -o /dev/null -w '%{http_code}' http://httpbin.org/ip"
# 000

mbp 关掉 Tailscale 后,IPv4 仍然不通。

排除清单#

到这里排除了几乎所有常见嫌疑:

假设排除方式
Tailscale blackhole 路由停掉 tailscaled + 检查 iptables/路由表
Tailscale Network ExtensionmacOS 上完全 tailscale down
Fios 防火墙日志无出站阻断
Fios NAT 状态Fios 重启后问题依旧
CGNAT同一公网 IP 出去,mini 通 mbp 不通,CGNAT 无法区分
ClashX 代理rp 上没有 ClashX 也不通
iptables/pf 残留rp 干净的 iptables,mbp 关了 Tailscale
DNS 问题三台设备解析结果一致

排了 8 轮,一度以为走进了死胡同。直到翻出两周前的排查记录——同一台 rp,同样的症状,已经找到过根因了。

真凶:ICMP Redirect 触发 Fios MAC 封锁#

3 月 17 日在 rp 上排查过完全相同的问题。当时的发现:

现象链#

  1. Docker 和 Tailscale 都需要 ip_forward=1
  2. Linux 默认 net.ipv4.conf.all.send_redirects=1
  3. ip_forward=1 + send_redirects=1 时,设备会向网关发送 ICMP Type 5 (Redirect) 消息——告诉路由器”这个包可以直接发给目标,不用经过我”
  4. Verizon Fios 路由器收到 ICMP Redirect 后,将发送方的 MAC 地址静默封锁
  5. 封锁方式:允许 TCP SYN/SYN-ACK(三次握手正常),但丢弃后续的 TCP 数据包
  6. ICMP ping 不受影响,IPv6 不受影响——只针对 IPv4 TCP 数据传输

这就解释了所有观察到的症状:connect 成功、sendto 成功、但 recv 永远超时。

验证:MAC 轮换立即恢复#

上次在 rp 上的验证:

Terminal window
# 查看当前 MAC
cat /sys/class/net/wlan0/address
# 2c:cf:67:46:7f:ba
# 换一个 MAC
sudo ip link set wlan0 down
sudo ip link set dev wlan0 address 2c:cf:67:46:7f:bb
sudo ip link set wlan0 up
# 等待重新关联和 DHCP
sleep 5
# 立即恢复
curl -4 -s http://1.1.1.1 -o /dev/null -w '%{http_code}'
# 301

换了 MAC 的最后一个字节,IPv4 立刻恢复。确认是 Fios 路由器按 MAC 地址做的封锁。

长期修复#

禁止设备发送 ICMP Redirect:

/etc/sysctl.d/99-no-redirects.conf
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
Terminal window
sudo sysctl -p /etc/sysctl.d/99-no-redirects.conf

这样即使 ip_forward=1,设备也不会发 ICMP Redirect,Fios 路由器就不会触发 MAC 封锁。

为什么这次又中招了#

rp 在这两周内重启过(Tailscale 掉线、Docker 更新等),99-no-redirects.conf 可能在某次系统更新或配置重置中丢失了。ip_forward 被 Docker 和 Tailscale 自动开启,send_redirects 回到了默认的 1,ICMP Redirect 重新触发,Fios 再次封锁 MAC。

mbp 的情况类似——Tailscale exit node 广播 0.0.0.0/0 加上 Parallels Desktop 的网桥(bridge100),都需要 IP forwarding。虽然 macOS 的 net.inet.ip.forwarding 检查结果是 0,但 Tailscale 的 Network Extension 和 Parallels 的虚拟网络可能通过其他机制转发了包含 ICMP Redirect 的流量。

为什么 Fios 路由器对 ICMP Redirect 反应这么极端?#

这其实不是 bug,而是一种安全防护。ICMP Redirect 消息可以被用来做中间人攻击——恶意设备发送伪造的 Redirect 告诉路由器”把流量发给我”。Fios 路由器检测到 LAN 内设备发送 Redirect 后,判定该设备可能在进行 ARP/ICMP 劫持,于是对其实施 MAC 级别的流量过滤。

这个防护逻辑本身没问题,问题在于:

  1. 完全不记日志——Firewall Log、Security Log 里都没有任何记录
  2. 不通知用户——设备列表里也没有标记
  3. 只断数据不断连接——TCP 握手正常,ping 正常,让你觉得网络是通的
  4. 重启路由器不清除——MAC 黑名单持久化存储

这种”隐形封锁”让排查极其困难。如果不是上次偶然试了 MAC 轮换,可能到现在还在怀疑 Tailscale 或 CGNAT。

修复清单#

rp (Linux)#

Terminal window
# 1. 确认 sysctl 配置存在
cat /etc/sysctl.d/99-no-redirects.conf
# net.ipv4.conf.all.send_redirects = 0
# net.ipv4.conf.default.send_redirects = 0
# 2. 如果不存在,创建它
echo -e "net.ipv4.conf.all.send_redirects = 0\nnet.ipv4.conf.default.send_redirects = 0" | \
sudo tee /etc/sysctl.d/99-no-redirects.conf
# 3. 应用
sudo sysctl -p /etc/sysctl.d/99-no-redirects.conf
# 4. 轮换 MAC 解除封锁
sudo ip link set wlan0 down
sudo ip link set dev wlan0 address $(python3 -c "import random; print(':'.join(['%02x' % (random.randint(0,255) if i else random.randint(0,255) & 0xfe | 0x02) for i in range(6)]))")
sudo ip link set wlan0 up

mbp (macOS)#

macOS 上禁用 ICMP Redirect:

Terminal window
sudo sysctl -w net.inet.ip.redirect=0

持久化需要创建 Launch Daemon。轮换 MAC:

Terminal window
# macOS 使用 Private Wi-Fi Address 功能可以自动轮换
# 或手动:
sudo ifconfig en0 ether $(openssl rand -hex 6 | sed 's/\(..\)/\1:/g; s/.$//')

Tailscale exit node 清理#

虽然不是直接原因,但 RouteAll: true + 空 ExitNodeID 的配置残留也应该清理:

Terminal window
tailscale up --exit-node="" --reset

这是 Tailscale #18923 记录的 bug——macOS GUI 关闭 exit node 后不清除 RouteAll。用 --reset 可以正确重置。

经验总结#

  1. Docker + Tailscale 在家用路由器后面是高危组合。两者都开 ip_forward,而家用路由器(至少 Verizon Fios G3100)会把 ICMP Redirect 视为攻击行为。任何跑 Docker 的 Linux 设备连到 Fios WiFi 上,都应该预防性地关闭 send_redirects

  2. “隐形封锁”是最难排查的问题类型。TCP 握手正常让你以为连接没问题,ping 正常让你排除了网络层,而路由器日志里什么都没有。当排除了所有软件层因素后,该考虑”网络设备本身在做什么”。

  3. 跨会话记忆很重要。这个问题两周前已经解决过一次。如果不是翻出了上次的排查记录,可能又要花好几个小时走完同样的弯路。

  4. 排查方法论回顾:

    • curl -4 vs curl -6 分离 IPv4/IPv6 问题
    • 同一 LAN 不同设备对比(控制变量)
    • strace -e trace=network 定位到系统调用层
    • ssh -o ProxyJump= 绕过 Tailscale 依赖做独立测试
    • MAC 轮换作为”是否被路由器封锁”的终极验证

关联文章#

这台 rp 和这个网络环境之前也出过不少幺蛾子:

Verizon Fios 的隐形 MAC 封锁:ICMP Redirect 触发 IPv4 全断
https://blog.lishuyu.top/posts/tailscale-exit-node-ipv4-黑洞排查/
作者
猫猫魔女
发布于
2026-03-31
许可协议
CC BY-NC-SA 4.0