Skip to content

Instantly share code, notes, and snippets.

@malash
Last active August 19, 2026 12:55
Show Gist options
  • Select an option

  • Save malash/b1f729f97d631565fbd1ed48deccae5c to your computer and use it in GitHub Desktop.

Select an option

Save malash/b1f729f97d631565fbd1ed48deccae5c to your computer and use it in GitHub Desktop.
Public IPv6 for VMs on OVH Proxmox (ndppd + SLAAC)

Public IPv4 (NAT) + IPv6 (SLAAC) for VMs on OVH Proxmox

⚠️ Vibe coded — use with caution. This project was built entirely through AI-assisted vibe coding and has not been audited. The implementation may contain subtle bugs or edge cases that were never considered. Review carefully and use at your own risk.

Background

OVH gives you a single public IPv4 (bound to your server's MAC) and an IPv6 /64 (here 2001:db8:1234:5678::/64). Neither an extra IPv4 subnet nor the /64 is routed to you — OVH filters IPv4 by MAC, and treats the /64 as on-link on the host's WAN interface, resolved per-address via NDP.

So guests get network in two independent ways:

  • IPv4 — private NAT. OVH won't route extra IPv4 to guest MACs, so guests live on a private subnet (10.10.10.0/24) behind the host's public IP via MASQUERADE. A DHCP server (dnsmasq) hands out those addresses.
  • IPv6 — public SLAAC. The /64 is publicly routable. radvd advertises it on the bridge so VMs auto-configure a public address via SLAAC; ndppd answers NDP on the WAN so the outside world can reach them.
  • radvd without ndppd: VMs get v6 addresses but return traffic is dropped by the OVH gateway — it won't actually work.
  • No DHCP server: guests with ip=dhcp never get an IPv4 address. This is the single most common gotcha.

Required base topology (read this first)

A stock OVH Proxmox install puts the public IP on vmbr0 and bridges vmbr0 to the physical NIC (bridge-ports enp1s0f0). This guide needs the opposite layout:

  • Public IPv4/IPv6 live directly on the physical NIC (enp1s0f0).
  • vmbr0 is a pure internal bridge (bridge-ports none) that guests attach to — it carries the private NAT gateway (10.10.10.1) and the IPv6 prefix gateway (::ffff/64).

If you are on the stock bridged layout, you must convert to this first (see step 1). Guests always attach to vmbr0.

                              ┌─ IPv6: radvd advertises the /64  → VM SLAACs a PUBLIC address
OVH gw ──NDP──▶ enp1s0f0 (WAN) ──forward──▶ vmbr0 ─────────────────────────────▶ guest
             └ ndppd replies for /64        (10.10.10.1, ::ffff/64)
                                            └─ IPv4: MASQUERADE + dnsmasq DHCP → VM gets 10.10.10.x (private)

Substitute for your setup: WAN NIC enp1s0f0, public v4 203.0.113.10/24, prefix 2001:db8:1234:5678::/64, bridge vmbr0, NAT subnet 10.10.10.0/24.


Configuration

1. Network layout (/etc/network/interfaces)

⚠️ This step can drop your SSH. Moving the public IP from the bridge to the physical NIC changes the default route. Do it with OVH KVM/IPMI (or the rescue console) available, and back up first: cp /etc/network/interfaces /etc/network/interfaces.bak.

Target file (public IPs on the NIC, vmbr0 internal with NAT + SLAAC gateway):

auto lo
iface lo inet loopback

# --- WAN: public IPs live directly on the physical NIC ---
auto enp1s0f0
iface enp1s0f0 inet static
    address 203.0.113.10/24
    gateway 203.0.113.254
    hwaddress aa:bb:cc:dd:ee:ff          # keep the OVH-registered MAC of this server

iface enp1s0f0 inet6 static
    address 2001:db8:1234:5678::1/128
    gateway 2001:db8:1234:56ff:ff:ff:ff:ff   # OVH's v6 gateway (see the OVH panel)

# --- vmbr0: internal bridge for guests (NAT IPv4 + SLAAC IPv6) ---
auto vmbr0
iface vmbr0 inet static
    address 10.10.10.1/24
    bridge-ports none
    bridge-stp off
    bridge-fd 0
    post-up   sysctl -w net.ipv4.ip_forward=1
    post-up   iptables  -t nat -A POSTROUTING -s 10.10.10.0/24 ! -d 10.10.10.0/24 -o enp1s0f0 -j MASQUERADE
    post-down iptables  -t nat -D POSTROUTING -s 10.10.10.0/24 ! -d 10.10.10.0/24 -o enp1s0f0 -j MASQUERADE

iface vmbr0 inet6 static
    address 2001:db8:1234:5678::ffff/64
    post-up   sysctl -w net.ipv6.conf.all.forwarding=1

Apply:

ifreload -a

Notes:

  • hwaddress must stay the MAC OVH registered for the server, otherwise OVH's gateway drops your traffic. On a stock install it's the physical NIC's own MAC (what vmbr0 was spoofing before).
  • ! -d 10.10.10.0/24 skips NAT for guest-to-guest traffic inside the subnet.
  • Both net.ipv4.ip_forward (for NAT) and net.ipv6.conf.all.forwarding (for IPv6 routing) are set here via post-up, so they persist across reboots.

2. Install and configure ndppd (NDP proxy on the WAN NIC)

apt install -y ndppd
cat > /etc/ndppd.conf <<'EOF'
route-ttl 30000
proxy enp1s0f0 {
   router yes
   timeout 500
   ttl 30000
   rule 2001:db8:1234:5678::/64 {
      static
   }
}
EOF
systemctl enable --now ndppd

ndppd ships as a SysV init script, not a native systemd unit. systemctl enable --now ndppd enables it at boot but the systemd-sysv-install shim often does not actually start the running process — you'll see enabled but inactive. Verify and start it explicitly if needed:

systemctl is-active ndppd        # if this prints "inactive":
/etc/init.d/ndppd start
systemctl is-active ndppd        # should now be "active"

proxy must be the WAN NIC (that's where the NDP queries arrive), not the bridge. static answers NDP for the entire /64, which suits SLAAC's dynamic addresses (a harmless "low prefix" warning is expected).

3. Install and configure radvd (SLAAC on the bridge)

apt install -y radvd
cat > /etc/radvd.conf <<'EOF'
interface vmbr0
{
    AdvSendAdvert on;
    MinRtrAdvInterval 3;
    MaxRtrAdvInterval 10;
    prefix 2001:db8:1234:5678::/64
    {
        AdvOnLink on;
        AdvAutonomous on;
        AdvRouterAddr on;
    };
    RDNSS 2001:4860:4860::8888 2606:4700:4700::1111 { };
};
EOF
systemctl enable --now radvd

radvd must run on the internal bridge (vmbr0), never on a bridge that still has a physical port to OVH — otherwise its Router Advertisements leak onto OVH's segment (rogue RA).

4. Install and configure dnsmasq (DHCP for the NAT subnet)

Guests configured with ip=dhcp (the Proxmox default) get an IPv4 address only if a DHCP server is listening on vmbr0. Without this they stay address-less on IPv4 no matter how correct the NAT is.

apt install -y dnsmasq
cat > /etc/dnsmasq.d/vmbr0-nat.conf <<'EOF'
# NAT DHCP for vmbr0 (managed manually)
interface=vmbr0
bind-interfaces
# do NOT run a DNS server (avoid an open resolver on a public IP)
port=0

# --- IPv4 DHCP ---
dhcp-range=10.10.10.50,10.10.10.200,255.255.255.0,12h
dhcp-option=option:router,10.10.10.1
dhcp-option=option:dns-server,1.1.1.1,8.8.8.8

# --- IPv6 RA + DHCPv6 (ULA for stable internal addressing) ---
enable-ra
dhcp-range=fd00:10:10:10::50,fd00:10:10:10::200,slaac,ra-names,64,12h
dhcp-option=option6:dns-server,[2606:4700:4700::1111],[2001:4860:4860::8888]
EOF
systemctl enable --now dnsmasq
  • port=0 makes dnsmasq a DHCP/RA-only server — it does not answer DNS, so you don't expose an open resolver on your public IP.
  • IPv6 is handled by radvd (public /64 SLAAC). dnsmasq additionally advertises a ULA (fd00:…) and hands out DNS servers over DHCPv6 — useful for stable internal addressing. Running both RA sources on vmbr0 is intentional and coexists fine.
  • After starting dnsmasq, existing guests may need a lease refresh: reboot the guest, or inside it run dhclient -r eth0 && dhclient eth0 (or ifreload/netplan apply).

Verify

Host:

ip -br addr show enp1s0f0                    # public v4 + v6 on the NIC
ip -br addr show vmbr0                        # 10.10.10.1/24 + ::ffff/64
sysctl net.ipv4.ip_forward net.ipv6.conf.all.forwarding   # both = 1
systemctl is-active ndppd radvd dnsmasq       # all active
ss -lunp | grep ':67'                         # dnsmasq listening for DHCP on vmbr0
cat /var/lib/misc/dnsmasq.leases              # leases handed to guests

Guest (NIC bridged to vmbr0):

ip -4 addr show dev eth0                       # should get a 10.10.10.x (private, via DHCP)
ping -c3 1.1.1.1                               # IPv4 outbound via NAT
ip -6 addr show dev eth0                        # should auto-get a 2001:db8:1234:5678:... PUBLIC address (SLAAC)
ping6 -c3 2001:4860:4860::8888                  # IPv6 outbound = whole path (incl. ndppd) works

Inbound IPv6: from an external host (not the Proxmox host — it reaches the VM directly over the bridge, bypassing ndppd) ping the VM's public address:

ping6 2001:db8:1234:5678:xxxx:xxxx:xxxx:xxxx

All of the above working = done. A reboot re-check is recommended to confirm it all comes up automatically.


Troubleshooting

  • VM has no IPv4 / no 10.10.10.x: dnsmasq isn't running, isn't bound to vmbr0, or the guest isn't on vmbr0. Check systemctl is-active dnsmasq and ss -lunp | grep :67.
  • ndppd shows enabled but inactive: it's a SysV service — systemctl --now may not have started it. Run /etc/init.d/ndppd start.
  • VM got no public IPv6 address: check the VM NIC is on vmbr0 with SLAAC enabled (accept_ra=1) and systemctl is-active radvd.
  • No IPv6 outbound: net.ipv6.conf.all.forwarding must be 1 and ndppd must be active.
  • No IPv6 inbound: test from an external host (the Proxmox host reaches VMs directly, bypassing ndppd); confirm ndppd proxy is the WAN NIC and the guest firewall allows ICMPv6.

Security: IPv4 guests are behind NAT, but every guest's IPv6 is a public, directly reachable address — firewall your guests accordingly (guest-side, or Proxmox's firewall=1 on the VM NIC).

OVH Proxmox 给 VM 配置 IPv4(NAT)+ IPv6(SLAAC)

⚠️ 由 AI 辅助编写,请谨慎使用。 本项目完全通过 AI 辅助的"氛围编程"构建,未经审计。实现中可能存在难以察觉的 bug 或从未被考虑到的边界情况。请仔细审查,风险自负。

背景

OVH 只给你一个公网 IPv4(绑定服务器 MAC)和一个 IPv6 /64 网段(本例 2001:db8:1234:5678::/64)。 额外的 IPv4 子网、以及这个 /64,都不是路由过来的——IPv4 按 MAC 过滤,/64 则被当作挂在母机 WAN 链路上、靠 NDP 逐个解析地址。

所以 guest 上网靠两套彼此独立的机制:

  • IPv4 —— 私网 NAT。 OVH 不会把额外 IPv4 路由给 guest 的 MAC,因此 guest 待在私网 (10.10.10.0/24)里,通过 MASQUERADE 借母机公网 IP 出网。地址由 DHCP 服务器(dnsmasq) 下发。
  • IPv6 —— 公网 SLAAC。 /64 是公网可路由的。radvd 在网桥上广播它,VM 自动 SLAAC 拿到一个公网 地址;ndppd 在 WAN 口代答 NDP,让外网找得到它们。
  • 只用 radvd、不用 ndppd:VM 拿得到 v6 地址,但回程流量会被 OVH 网关丢弃,实际不通。
  • 没有 DHCP 服务器:ip=dhcp 的 guest 永远拿不到 IPv4——这是最常见的坑。

前置架构(务必先读)

OVH 装好的 Proxmox 默认把公网 IP 放在 vmbr0 上、并让 vmbr0 桥接物理网卡 (bridge-ports enp1s0f0)。本文需要的是相反的布局:

  • 公网 IPv4/IPv6 直接放在物理网卡 enp1s0f0 上。
  • vmbr0 是一个纯内网桥(bridge-ports none),供 guest 接入——它承载私网 NAT 网关 (10.10.10.1)和 IPv6 前缀网关(::ffff/64)。

如果你还是默认的桥接式布局,必须先按步骤 1 改造。guest 一律接到 vmbr0。

                              ┌─ IPv6:radvd 广播 /64   → VM SLAAC 拿到公网地址
OVH 网关 ──NDP──▶ enp1s0f0 (WAN) ──转发──▶ vmbr0 ──────────────────────────────▶ guest
              └ ndppd 代答 /64             (10.10.10.1, ::ffff/64)
                                          └─ IPv4:MASQUERADE + dnsmasq DHCP → VM 拿到 10.10.10.x(私网)

按需替换:WAN 网卡 enp1s0f0、公网 v4 203.0.113.10/24、网段 2001:db8:1234:5678::/64、 网桥 vmbr0、NAT 子网 10.10.10.0/24。


配置步骤

1. 网络布局(/etc/network/interfaces)

⚠️ 这一步可能让你 SSH 断线。 把公网 IP 从网桥挪到物理网卡会改变默认路由。请在有 OVH KVM/IPMI (或救援控制台)的情况下操作,并先备份:cp /etc/network/interfaces /etc/network/interfaces.bak。

目标文件(公网 IP 在网卡,vmbr0 内网桥带 NAT + SLAAC 网关):

auto lo
iface lo inet loopback

# --- WAN: public IPs live directly on the physical NIC ---
auto enp1s0f0
iface enp1s0f0 inet static
    address 203.0.113.10/24
    gateway 203.0.113.254
    hwaddress aa:bb:cc:dd:ee:ff          # keep the OVH-registered MAC of this server

iface enp1s0f0 inet6 static
    address 2001:db8:1234:5678::1/128
    gateway 2001:db8:1234:56ff:ff:ff:ff:ff   # OVH's v6 gateway (see the OVH panel)

# --- vmbr0: internal bridge for guests (NAT IPv4 + SLAAC IPv6) ---
auto vmbr0
iface vmbr0 inet static
    address 10.10.10.1/24
    bridge-ports none
    bridge-stp off
    bridge-fd 0
    post-up   sysctl -w net.ipv4.ip_forward=1
    post-up   iptables  -t nat -A POSTROUTING -s 10.10.10.0/24 ! -d 10.10.10.0/24 -o enp1s0f0 -j MASQUERADE
    post-down iptables  -t nat -D POSTROUTING -s 10.10.10.0/24 ! -d 10.10.10.0/24 -o enp1s0f0 -j MASQUERADE

iface vmbr0 inet6 static
    address 2001:db8:1234:5678::ffff/64
    post-up   sysctl -w net.ipv6.conf.all.forwarding=1

应用:

ifreload -a

说明:

  • hwaddress 必须保留 OVH 注册的那个 MAC,否则 OVH 网关会丢弃你的流量。默认装机时它就是物理网卡 自己的 MAC(也就是之前 vmbr0 在伪装的那个)。
  • ! -d 10.10.10.0/24 让子网内 guest 互访不走 NAT。
  • net.ipv4.ip_forward(给 NAT 用)和 net.ipv6.conf.all.forwarding(给 IPv6 路由用)都在这里用 post-up 打开,重启后依旧生效。

2. 安装配置 ndppd(在 WAN 网卡上代答 NDP)

apt install -y ndppd
cat > /etc/ndppd.conf <<'EOF'
route-ttl 30000
proxy enp1s0f0 {
   router yes
   timeout 500
   ttl 30000
   rule 2001:db8:1234:5678::/64 {
      static
   }
}
EOF
systemctl enable --now ndppd

ndppd 是 SysV init 脚本,不是原生 systemd 单元。 systemctl enable --now ndppd 能设成开机启动, 但 systemd-sysv-install 兼容层常常并不会真正拉起进程——你会看到 enabled 但 inactive。需要时 显式启动:

systemctl is-active ndppd        # 若输出 "inactive":
/etc/init.d/ndppd start
systemctl is-active ndppd        # 现在应为 "active"

proxy 必须填 WAN 网卡(NDP 查询来自这里),不是网桥。 static 会代答整个 /64,配合 SLAAC 动态地址最省事(会有一条无害的 low prefix 警告)。

3. 安装配置 radvd(在网桥上做 SLAAC)

apt install -y radvd
cat > /etc/radvd.conf <<'EOF'
interface vmbr0
{
    AdvSendAdvert on;
    MinRtrAdvInterval 3;
    MaxRtrAdvInterval 10;
    prefix 2001:db8:1234:5678::/64
    {
        AdvOnLink on;
        AdvAutonomous on;
        AdvRouterAddr on;
    };
    RDNSS 2001:4860:4860::8888 2606:4700:4700::1111 { };
};
EOF
systemctl enable --now radvd

radvd 必须跑在内网桥(vmbr0)上,绝不能跑在还带物理口连着 OVH 的桥上——否则它的 Router Advertisement 会泄漏到 OVH 的二层段(rogue RA)。

4. 安装配置 dnsmasq(给 NAT 子网发 DHCP)

配置为 ip=dhcp 的 guest(Proxmox 默认值)只有当 vmbr0 上有 DHCP 服务器监听时才拿得到 IPv4。 否则不管 NAT 配得多正确,guest 的 IPv4 永远是空的。

apt install -y dnsmasq
cat > /etc/dnsmasq.d/vmbr0-nat.conf <<'EOF'
# NAT DHCP for vmbr0 (managed manually)
interface=vmbr0
bind-interfaces
# do NOT run a DNS server (avoid an open resolver on a public IP)
port=0

# --- IPv4 DHCP ---
dhcp-range=10.10.10.50,10.10.10.200,255.255.255.0,12h
dhcp-option=option:router,10.10.10.1
dhcp-option=option:dns-server,1.1.1.1,8.8.8.8

# --- IPv6 RA + DHCPv6 (ULA for stable internal addressing) ---
enable-ra
dhcp-range=fd00:10:10:10::50,fd00:10:10:10::200,slaac,ra-names,64,12h
dhcp-option=option6:dns-server,[2606:4700:4700::1111],[2001:4860:4860::8888]
EOF
systemctl enable --now dnsmasq
  • port=0 让 dnsmasq 只做 DHCP/RA,不应答 DNS,这样就不会在公网 IP 上暴露开放解析器。
  • IPv6 由 radvd 负责(公网 /64 SLAAC)。dnsmasq 额外广播一个 ULA(fd00:…)并通过 DHCPv6 下发 DNS——用于稳定的内网寻址。在 vmbr0 上同时跑两个 RA 源是刻意为之,能正常共存。
  • 启动 dnsmasq 后,已有的 guest 可能需要刷新租约:重启 guest,或在其内部执行 dhclient -r eth0 && dhclient eth0(或 ifreload/netplan apply)。

验证

母机:

ip -br addr show enp1s0f0                    # 公网 v4 + v6 在网卡上
ip -br addr show vmbr0                        # 10.10.10.1/24 + ::ffff/64
sysctl net.ipv4.ip_forward net.ipv6.conf.all.forwarding   # 均为 1
systemctl is-active ndppd radvd dnsmasq       # 均 active
ss -lunp | grep ':67'                         # dnsmasq 在 vmbr0 上监听 DHCP
cat /var/lib/misc/dnsmasq.leases              # 已发给 guest 的租约

Guest(网卡桥接到 vmbr0):

ip -4 addr show dev eth0                        # 应通过 DHCP 拿到 10.10.10.x(私网)
ping -c3 1.1.1.1                                # IPv4 经 NAT 出站
ip -6 addr show dev eth0                         # 应自动 SLAAC 拿到 2001:db8:1234:5678:... 公网地址
ping6 -c3 2001:4860:4860::8888                   # IPv6 出站通 = 整条链路(含 ndppd)正常

IPv6 入站:从外部机器(不是母机——母机走网桥直连 VM,会绕过 ndppd)ping VM 的公网地址:

ping6 2001:db8:1234:5678:xxxx:xxxx:xxxx:xxxx

以上全部通即完成。建议 reboot 后再验证一次开机自动生效。


排错

  • VM 没有 IPv4 / 拿不到 10.10.10.x:dnsmasq 没运行、没绑到 vmbr0,或 guest 没接在 vmbr0 上。 检查 systemctl is-active dnsmasq 和 ss -lunp | grep :67。
  • ndppd 显示 enabled 但 inactive:它是 SysV 服务,systemctl --now 可能没真正启动它。执行 /etc/init.d/ndppd start。
  • VM 没拿到公网 IPv6 地址:检查 VM 网卡是否在 vmbr0 上、是否开启 SLAAC(accept_ra=1),以及 systemctl is-active radvd。
  • IPv6 出站不通:net.ipv6.conf.all.forwarding 需为 1,且 ndppd 需 active。
  • IPv6 入站不通:从外部机器测(母机走网桥直连 VM、会绕过 ndppd);确认 ndppd proxy 填的是 WAN 网卡、 且 guest 防火墙放行 ICMPv6。

安全:IPv4 的 guest 在 NAT 后面,但每台 guest 的 IPv6 都是公网可直接访问的地址——请相应给 guest 配防火墙(guest 侧,或用 Proxmox 在 VM 网卡上的 firewall=1)。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment