在 cdc_ether(以太网模拟模式的蜂窝网卡/USB 网卡)这类二层广播接口(Broadcast/Ethernet)上配置跨网段的 IPv6 路由时,必须指定下一跳网关地址(via ra_router_ll),否则内核无法完成邻居发现(NDP)解析。
在 Linux 路由表中,带不带 via 代表两种完全不同的转发逻辑:
不带 via(ip -6 route add prefix/32 dev cdc_ether):
这被称为 On-link(直连/链路本地)路由。它告诉 Linux 内核:“目标 IP 就在 cdc_ether 这根网线上,不需要经过路由器转发。” 当内核发送数据包给 P-CSCF 时,会直接在 cdc_ether 接口上发送 NDP 邻居请求(Neighbor Solicitation, NS),广播询问:“谁拥有 pcscf_ip?请告诉我你的 MAC 地址。”
带 via(ip -6 route add prefix/32 via ra_router_ll dev cdc_ether):
这被称为 Gateway(网关/下一跳)路由。它告诉内核:“目标 IP 在远端网络,请把数据包交给下一跳路由器 ra_router_ll。” 此时内核只会向 ra_router_ll 请求 MAC 地址,并将目标 IP 为 P-CSCF 的数据包封装上路由器的 MAC 地址发送出去。
cdc_ether 这条链路上真正的对端设备只有调制解调器(ECM 接口);P-CSCF 在运营商核心网里,与终端的 /64 前缀根本不在同一个二层链路上。所以 dev-only 的写法会对 P-CSCF 地址发 NS——通常没人应答,邻居解析 INCOMPLETE/FAILED,包根本发不出去。加了 via fe80::Y 后,目的 MAC 改成调制解调器的,由它在网络层继续转发,这才符合真实拓扑。
部分 USB 4G/5G 模组固件为了兼容不规范的路由配置,会在内部开启“NDP 代答”(Proxy NDP)。当你的主机直接对 pcscf_ip 发送 NS 请求时,模组固件会假装自己是 P-CSCF 并回复自己的 MAC 地址,从而让不带 via 的直连路由“歪打正着”地跑通。一旦升级了模组固件、更换了网卡型号,或者模组切换了工作模式,模组不再替远端 IP 代答 NS 请求,内核就会报错 Destination unreachable: Address unreachable(通过 ip -6 neigh show dev cdc_ether 可以看到目标 P-CSCF IP 处于 FAILED 或 INCOMPLETE 状态)。
如何用 ip -6 route 获取 ra_router_ll
ra_router_ll=$(ip -6 route show default dev cdc_ether | awk ‘/via/ {for(i=1;i<=NF;i++) if($i=="via") print $(i+1); exit}')
或者
GW=$(ip -6 route show default dev cdc_ether | awk '/^default via/ {print $3; exit}')
或者
import json, subprocess
out = subprocess.check_output(
["ip", "-j", "-6", "route", "show", "default", "dev", "cdc_ether"]
)
gw = json.loads(out)[0]["gateway"] # fe80::...
如果默认路由已经被不小心删掉了,只要还没有清空邻居表,也可以通过 ip -6 neigh show dev cdc_ether | grep router 找到带有 router 标记的 fe80:: 地址。
另外一个方法:rdisc6 cdc_ether(ndisc6 包)主动发 RS,从应答源地址读 fe80::