IPv6 部署中两种完全不同的机房网络架构。
二层桥接模式(On-Link / Bridged)
三层路由模式(Routed Subnet)。
1. 架构 A:二层桥接(On-Link / 也就是需要门卫的模式)
- 机房特征:机房的上游路由器把分配给你的
/64网段当成一个“大局域网”。 - 行为模式:当外面有数据包要发给
...100(容器)时,路由器会在公网上大喊(发 NDP Solicitation):“/64里面的...100在哪?报上 MAC 地址!” - 应对方案:这就是我们前面讨论的场景,必须使用
ndppd代为回应,否则包就被丢了。
2. 架构 B:三层路由(Routed)
- 机房特征:机房(比如很多 Hetzner 的纯路由模式,或者部分云厂商)会给你宿主机的物理网卡分配一个独立的 IP(比如一个
/128或者链路本地地址fe80::),然后在机房的核心路由器上写了一条死路由(Static Route)。 - 行为模式:机房路由器的规则是:“凡是目标地址属于
2a0e:0000:1::/64的包,不要问任何人,直接闭着眼睛打包塞给主机的 MAC 地址”。 - 应对方案:在这种机房,完全不需要
ndppd。因为根本就没有人会发 NDP 请求问路。你只需要开启内核转发(sysctl net.ipv6.conf.all.forwarding=1),宿主机收到包后自己内部消化并转给容器就行了。
那么矛盾来了:如果你的机房是架构 B(不问路),为什么“保活脚本”能修好网络?
如果机房不需要问路,理论上网络永远不应该断。但实际上断了,而且 ping 了一下就好了,这通常说明上游存在有状态的拦截(Stateful Firewall / MAC 老化):
- 宿主机的网关 MAC 老化了: 虽然上游路由器不问
/64里的 IP,但它可能会忘记宿主机本身外网网卡的 MAC 地址。保活脚本实际上是刷新了宿主机与上游路由器的邻居状态。 - 机房的防 DDoS 防火墙(类似 OVH 或某些高防机房): 很多机房的边界防火墙是“有状态”的。为了防攻击,默认丢弃所有外部主动连接你的 IPv6 包。只有当你的机器主动向外发包(比如保活脚本去 ping 外网)时,防火墙才会“打开一个缺口(Punch Hole)”,允许回包进入。几分钟不发包,缺口就关了。
NDP 缓存老化/过期
- 为什么路由器会“忘记”?(正常的缓存机制)
无论是 IPv4 的 ARP 还是 IPv6 的 NDP,路由器内部都有一个“邻居缓存表(Neighbor Cache)”。
为了节省内存和应对网络拓扑变化,这些记录通常在几分钟到十几分钟没有流量后,状态就会从 REACHABLE(可达)变成 STALE(陈旧),最后被删除。
这是一个完全正常且健康的网络行为。
- 正常情况下,“忘记”后会发生什么?
当路由器收到发给你的数据包,但发现缓存里已经没有你的 MAC 地址时:
路由器会向所在的网段发送一个广播/多播(Neighbor Solicitation):“谁是 2a0e:0000:1::100?请回答!”
目标机器听到后,回复一个(Neighbor Advertisement):“是我,我的 MAC 地址是 xxx。”
路由器重新把你的信息写回缓存表,并把数据包发给你。
如何用一秒钟验证你的机房到底是哪种?
既然你怀疑机房可能根本不发 NDP 询问,我们可以抓包来“验明正身”。这能决定你到底需不需要留着 ndppd。
在 宿主机 上开一个终端,执行抓包:
tcpdump -i eth0 icmp6 -n然后,从外部(比如你的电脑,或者另一个服务器)去 ping 你的 LXC 容器 IP 2a0e:0000:1::100。
观察 tcpdump 的输出:
- 如果看到了
Neighbor Solicitation who has 2a0e:0000:1::100:说明机房是架构 A。机房在问路!必须保留ndppd。 - 如果没有看到任何 Solicitation 询问,直接看到了
Echo Request(Ping 请求包)飞进来:说明机房是架构 B。这种情况下,可以果断卸载ndppd,因为它纯属多余。
有的机房有MAC地址过滤--为了节省路由器资源,这个策略也很合理。具体配置以后再补充吧。比较折腾。我算是把这些坑都测了一遍。
这又是独立服务器/VPS 圈子里一个非常经典的“天坑”!你提到的这个机制,在网络安全中被称为 MAC 地址过滤(MAC Filtering) 或 端口安全(Port Security)。Hetzner、OVH等知名机房都严格开启了这个机制。
机房的交换机就像是一个死板的保安:“我这根网线插在你的物理网卡上,你的物理 MAC 地址是 AA:BB:CC:DD:EE:FF。只要是从这根网线发出来的包,源 MAC 地址必须是这个,凡是出现其他陌生的 MAC,立刻丢弃数据包,严重的话甚至会自动封禁你的物理网口!”
这就带来了一个巨大的冲突:你的 LXC 容器是虚拟出来的,它拥有一个属于自己的、随机生成的虚拟 MAC 地址。
面对这种“只认物理 MAC”的机房,绝对不能使用传统的“纯物理桥接”。你需要让宿主机充当“代理人”。以下是具体的处理方案,这也是 PVE 网络配置的核心进阶技巧:
核心解法:将“二层桥接”改为“三层路由” (Routed Setup)
如果你把物理网卡(如 eth0)直接桥接到 vmbr0,然后把容器也挂在 vmbr0 上,这就相当于在机房和容器之间拉了一根直通网线。容器的虚拟 MAC 会直接暴露给机房,立刻被拦截。
解决思路是:让数据包在宿主机内部“换个包装”。
1. 内部网桥完全独立(你目前应该已经做到了)
确保你的 LXC 容器连接的是一个没有绑定物理网卡的内部网桥(比如你的 vmbr1)。
- 在 PVE 的
/etc/network/interfaces中,vmbr1的配置里,bridge-ports必须是none。 - 这样,LXC 的数据包只能走到宿主机,绝对不会直接泄漏到公网物理线路上。
2. 发送方(LXC 发到公网):依靠内核转发“偷天换日”
当 LXC (内网) 想要访问 Google (公网) 时:
- LXC 将数据包发给网关(宿主机的
vmbr1)。这时的源 MAC 是 LXC 的虚拟 MAC。 - 宿主机收到包后,因为你开启了 内核转发(IP Forwarding),宿主机会进行跨网卡路由。
- 最关键的一步: 宿主机决定通过外网物理网卡(
eth0)将包发往机房。在以太网协议中,经过一次路由跃点,数据包的二层 MAC 头部会被完全拆掉重写。 - 宿主机会填上自己物理网卡
eth0的 MAC 地址,然后发给机房。 - 机房保安一看:源 MAC 是宿主机的,合法,放行!
(总结:只要开启了 sysctl net.ipv6.conf.all.forwarding=1,且没有把容器直接桥接到物理网卡上,发出去的包就永远只带有宿主机的合法 MAC。)
3. 接收方(公网发给 LXC):依靠 ndppd “代为举手”
机房的保安不仅查出去的人,也查进来的人。
当机房收到回包,要发给你的容器 IP ...100 时,保安会在网段里大喊:“谁是 ...100,把 MAC 地址告诉我!”
- 如果没有
ndppd,容器听不见,没人回。 - 因为你配置了
ndppd(门卫代理): 宿主机会立刻向机房回复:“是我,我的 MAC 地址是宿主机的物理 MAC!” - 保安一看,这个 MAC 地址在白名单里,合法!于是把本该给容器的数据包,交给了宿主机。
- 宿主机再通过内核路由,原封不动地内部转发给容器。
总结:你现在的架构已经是“终极答案”
回头看看你之前的配置:
- 你把网关设为了
vmbr1(宿主机内部接口)。 - 你开启了
sysctl转发。 - 你配置了
ndppd。
这套“路由模式 + NDP 代理”的组合拳,恰恰完美破解了“机房只认主机 MAC”的死局!
- 出去的时候:内核转发(IP Forwarding)充当了“重写 MAC”的掩护。
- 进来的时候:
ndppd充当了“使用合法 MAC 代领包裹”的代理。
在这个架构下,你的 LXC 虚拟 MAC 被死死地锁在了宿主机内部,机房的交换机永远只能看到你那张合法的物理网卡 MAC。你不需要再做任何额外的 MAC 地址伪装或修改了,放心用吧!
之前总结过全手动路由,这个是用ndppd配合内核转发的。
route-ttl 30000
# 这里的接口必须是连接外网(上级路由)的桥接网卡;注意选择-一般不能是eth0/ens18;查询路由表得到:与机房上游网关(fe80::1)通信的物理出口
#注意auto还是static;根据调试情况,还可以有单个地址的/128 {static}
proxy vmbr0 {
router yes
timeout 500
ttl 30000
# 这里填入你的整个 IPv6 段
rule 2a0e:xx:xx::/64 {
auto
}
}搞的比较精细的interface文件
source /etc/network/interfaces.d/*
auto lo
iface lo inet loopback
auto ens18
iface ens18 inet manual
auto vmbr0
iface vmbr0 inet static
address xx.xx.xx.12/24
gateway xx.xx.xx.1
dns-nameservers 1.1.1.1
bridge-ports ens18
bridge-stp off
bridge-fd 0
hwaddress ether 4e:11:07:11:87:4e
iface vmbr0 inet6 static
address 2a0e:xx:xx:xx::a/128
bridge-ports ens18
bridge-stp off
bridge-fd 0
# 顺序优化:先开转发,再加路由 # 开启NDP应答(需配合NDP服务)
post-up sysctl -w net.ipv6.conf.all.forwarding=1
post-up sysctl -w net.ipv6.conf.vmbr0.proxy_ndp=1
####
#忘记那些幻影,不接受任何 RA 公告
post-up sysctl -w net.ipv6.conf.vmbr0.accept_ra=0
post-up sysctl -w net.ipv6.conf.vmbr0.autoconf=0
# 路由添加(nud permanent参数无条件作为路由,不再需要用心跳包宝词汇网关记忆;注意mac地址⚠️变动)
#ip -6 neigh show 找出fe80::1 网关的mac地址
post-up ip -6 route add default via fe80::1 dev vmbr0 onlink || true
post-up ip -6 neigh replace fe80::1 lladdr 00:00:5e:60:02:07 dev vmbr0 nud permanent
# MSS 钳制优化
post-up ip6tables -t mangle -A FORWARD -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1400
post-down ip6tables -t mangle -D FORWARD -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1400
auto vmbr1
iface vmbr1 inet6 static
# 这里填入你想作为容器网关的公网 IPv6 地址
# 通常是你网段里的第一个地址,例如 ::1
# address 2a0e:xx:xx:xx::f/128
# 建议使用ULA地址
#PVE 的三层路由环境下,手动指定一个明确的 ULA 或公网地址作为网关,可以避免由于 MAC #地址变动或 NDP 发现失败导致的路由丢失,配置更加清晰稳健。
address fd10:1234:5678::1/64
bridge-ports none
bridge-stp off
bridge-fd 0
post-up ip link set vmbr1 up
# # 这能保证宿主机的 NDP 询问一定能到达容器
post-up echo 0 > /sys/class/net/vmbr1/bridge/multicast_snooping
# 关键:开启转发并关闭该接口的 RA 接收,防止宿主机自己被干扰
post-up sysctl -w net.ipv6.conf.vmbr1.forwarding=1
post-up sysctl -w net.ipv6.conf.vmbr1.accept_ra=0
# --- 保留并增加具体的容器路由 (精确、安全) ---
up ip -6 route add 2a0e:xx:xx:xx::100/128 dev vmbr1
up ip -6 route add 2a0e:xx:xx:xx::101/128 dev vmbr1
up ip -6 route add 2a0e:xx:xx:xx::102/128 dev vmbr1
iface vmbr1 inet static
address 10.0.0.1/24
bridge-ports none
bridge-stp off
bridge-fd 0
post-up iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -o vmbr0 -j MASQUERADE
post-down iptables -t nat -D POSTROUTING -s 10.0.0.0/24 -o vmbr0 -j MASQUERADE修改前的也记录下;
# post-up ip -6 neigh add fe80::1 lladdr 00:00:5e:60:02:07 dev vmbr0 || true
# 额外保鲜:每 15 秒强制刷新一次网关(应对那种极其健忘的交换机)
# post-up screen -d -m bash -c "while true; do ping -6 -c 1 fe80::1%vmbr0 > /dev/null; sleep 15; done"
#post-up ip -6 neigh add fe80::1 lladdr 00:00:5e:60:02:07 dev vmbr0 nud permanent上面的新配置用这部分替换了:
#ip -6 neigh show 找出fe80::1 网关的mac地址
post-up ip -6 route add default via fe80::1 dev vmbr0 onlink || true
post-up ip -6 neigh replace fe80::1 lladdr 00:00:5e:60:02:07 dev vmbr0 nud permanent补充个混杂模式的坑--解决后注意在配置文件里补充/持久化
症状:pve宿主机不能ping通网关
准备:先确认
sysctl -w net.ipv6.conf.all.forwarding=1
sysctl -w net.ipv6.conf.vmbr0.proxy_ndp=1对vmbr0的v6抓包--可以只筛选网关地址:
tcpdump -i vmbr0 -n icmp6 and host 2a0c:4ac1:4:xxx::f
tcpdump -i vmbr0 -n "icmp6 and (ip6 src 2a0c:4ac1:4::1 or ip6 dst 2a0c:4ac1:4::1)"终极排查:桥接混杂模式(Promiscuous Mode)
如果 vmbr0 是一个桥接接口,且它下面挂载了物理网卡,有时需要开启混杂模式才能接收到不属于自己 MAC 地址的包:
ip link set vmbr0 promisc on
# 如果 vmbr0 桥接了 eth0,eth0 也要开
ip link set eth0 promisc on开启混杂模式(Promiscuous Mode)通常是解决虚拟化网络“单通”或“完全不通”的最后一把钥匙。
在正常的网络工作流程中,网卡只接收目的地 MAC 地址是自己的包。但在桥接环境下,宿主机的物理网卡需要接收目的地是 VM/LXC 虚拟 MAC 地址的包并将其转交给 vmbr0。
哪些情况下混杂模式会默认开启?
在 Linux 系统中,混杂模式通常不会在普通物理网卡上默认开启(为了节省 CPU 资源并提高安全性),但在以下几种场景中,系统或内核会自动触发它:
1. 运行抓包工具(最常见)
当你运行 tcpdump 或 wireshark 时,这些工具会通过内核调用临时开启接口的混杂模式,以便查看经过网卡的所有流量。这也是为什么很多工程师发现“只要我开着抓包网络就通,一关抓包网络就断”的灵异现象。
2. Linux Bridge (虚拟网桥) 的特定行为
在标准的 Linux Bridge 驱动中,当你将一个物理网卡(如 eth0)通过 brctl addif 或 ip link set eth0 master vmbr0 强行加入网桥时,内核通常会尝试自动开启该物理接口的混杂模式。
- 例外情况:如果你的网桥启用了
vlan_filtering(VLAN Aware),或者底层驱动由于某种硬件卸载(Hardware Offload)特性限制,自动开启可能会失败,需要手动干预。
3. 使用 MACVLAN 或 IPVLAN
如果你在容器环境(如 Docker 的 MACVLAN 驱动)中使用这些虚拟化技术,父接口(Parent Interface)通常会被强制进入混杂模式,否则它无法识别并分发属于子接口 MAC 地址的流量。
4. 网卡绑定(Bonding/Teaming)
在某些复杂的网卡绑定模式下(例如模式 0、5、6),为了负载均衡和接收来自多个路径的包,内核会自动激活成员网卡的混杂模式。
为什么在你的环境下需要手动开启?
既然按理说桥接应该自动处理,为什么你必须手动执行?主要有以下几个“坑”:
- Proxmox/Debian 的配置遗留:在
/etc/network/interfaces中,如果 bridge 的定义不够完整,或者物理接口没有正确触发UP状态下的标志位同步,就会导致混杂模式未激活。 - MAC 地址不一致:如果你的
vmbr0拥有一个 MAC 地址,而物理网卡eth0拥有另一个,且机房网关只针对其中一个 MAC 发回响应,物理网卡在非混杂模式下会直接丢弃掉给网桥的包。 - 硬件过滤限制:某些 VPS 供应商(特别是使用了 SR-IOV 或特定虚拟化驱动的环境)在底层屏蔽了自动开启混杂模式的指令,导致你必须在操作系统层面显式声明。
💡 进阶建议:持久化配置
手动执行 ip link set ... promisc on 在重启后会失效。建议将其写入 /etc/network/interfaces 以实现持久化:
iface eth0 inet manual
# 在物理网卡启动后强制开启混杂模式
post-up ip link set $IFACE promisc on
auto vmbr0
iface vmbr0 inet6 static
address 2a0c:4ac1:4:xx::xx/64
gateway 2a0c:4ac1:4::1
bridge-ports eth0
bridge-stp off
bridge-fd 0
# 同样可以给网桥加上
post-up ip link set $IFACE promisc on