基于已有组网的跨宿主机的vm互联
title
2026-03-17date
description

当然这是在已经有组网的前提下,并且预期多台宿主机上的虚拟机能互相访问对方和宿主机。

既然宿主机之间已经通过 Babel 运行了动态路由,我们完全可以利用 Babel 的特性,让虚拟机直接使用组网内的 IPv6 进行原生路由互联和双栈外部访问。

                                Babel Network (fdcc::/16)
                                           |
                +--------------------------v--------------------------+
                |                          |                          |
      +---------+----------+               |                +---------+----------+
      |      Host A        |               |                |      Host B        |
      |     (fdcc::1)      |               |                |     (fdcc::2)      |
      +---------+----------+               |                +---------+----------+
                |                          |                          |
      +---------+----------+               |                +---------+----------+
      |     Bridge br0     |               |                |     Bridge br0     |
      |   (fdcc:1::1/64)   |               |                |   (fdcc:2::1/64)   |
      +---------+----------+               |                +---------+----------+
                |                          |                          |
      +---------v----------+               |                +---------v----------+
      |        VM-A        |               |                |        VM-B        |
      |   (fdcc:1::2/64)   |               |                |   (fdcc:2::2/64)   |
      |   (172.0.0.1/24)   |               |                |   (172.0.0.2/24)   |
      +--------------------+               |                +--------------------+

假设我们已经建立了一个底层网络,如这个,我们的组网大网段设定为 fdcc::/16。以两台宿主机的规模举例,

宿主机 A:

  • Babel 内网 IP:fdcc::1
  • 虚拟机网桥 (br0):fdcc:1::1/64
  • VM-A 分配 IP:172.0.0.1/24

宿主机 B:

  • Babel 节点 IP:fdcc::2
  • 虚拟机网桥 (br0):fdcc:2::1/64
  • VM-B 分配 IP:172.0.0.2/24

我使用incus创建和管理虚拟机,所以可以有个preseed来预设配置:

{
  networks = [
    {
      config = {
        "ipv6.address" = "fdcc:1::1/64" # 上文的虚拟机网桥 (br0) 地址
        "ipv6.nat" = "false";
        "ipv6.routing" = "true";
        "ipv4.address" = "none";
        "dns.mode" = "none";
      };
      name = "br0";
      type = "bridge";
    }
  ];
  profiles = [
    {
      devices = {
        eth0 = {
          name = "eth0";
          type = "nic";
          network = "br0";
        };
        root = {
          path = "/";
          pool = "default";
          size = "64GiB";
          type = "disk";
        };
      };
      config = {
        "limits.cpu" = "4";
        "limits.memory" = "4GiB";
        "security.secureboot" = "false";
      };
      name = "default";
    }
  ];
}

使用

incus create images:debian/14/amd64 node-2 --vm

创建虚拟机,start之后预期是仅有一个符合之前设定的br内网前缀的/64地址,

> incus ls
+--------+---------+------+------------------------------------+-----------------+-----------+
|  NAME  |  STATE  | IPV4 |                IPV6                |      TYPE       | SNAPSHOTS |
+--------+---------+------+------------------------------------+-----------------+-----------+
| node-2 | RUNNING |      | fdcc:3::1266:6aff:feac:78bd (eth0) | VIRTUAL-MACHINE | 0         |
+--------+---------+------+------------------------------------+-----------------+-----------+

在宿主机的bird内添加一条抓br0的路由的声明:

protocol direct ext {
  ipv6;
  interface "br0";
}

改改过滤器把路由宣告到宿主机之间的组网中:

# ... 前置基础配置省略 ...
define HORTUS_PREFIX = fdcc::/16;
define HORTUS_FIELD = [ fdcc::/16+ ];

# 1. 捕获面向虚拟机的网桥接口
protocol direct ext {
  ipv6;
  interface "br0";
}

# 2. 过滤规则:允许重分发 ext 协议捕获的直连路由
filter to_hortus {
  if in_hortus() && (source = RTS_BABEL || source = RTS_DEVICE) then accept;
  if proto = "ext" then accept; # 将 br0 的网段放行
  reject;
};

# 3. Babel 协议配置
protocol babel {
  interface "zt*" {
    type wired;
    # ... 认证参数省略 ...
    extended next hop yes; # 开启 v4-over-v6 支持
  };
  ipv6 {
    import where in_hortus();
    export filter to_hortus; # 将 br0 的路由宣告出去
  };
};

当宿主机 A 的 BIRD 启动后,宿主机 B 的路由表中就会动态学习到 fdcc:1::/64 这个网段,且下一跳指向宿主机 A 的 zt 接口地址。

因为后续计划用babel的 extended next hop (ietf rfc9229) 特性,把v4包下一跳设成v6,所以内核默认设置可能会狠狠拦它,得稍微调一下:

开启转发与关闭源地址校验 (rp_filter)

# 开启 IPv4 和 IPv6 转发
sysctl -w net.ipv4.ip_forward=1
sysctl -w net.ipv6.conf.all.forwarding=1

# 关闭涉及路由接口的严格逆向路径过滤
sysctl -w net.ipv4.conf.all.rp_filter=0
sysctl -w net.ipv4.conf.default.rp_filter=0
sysctl -w net.ipv4.conf.br0.rp_filter=0
# Babel 外部物理或隧道接口也要关闭
sysctl -w net.ipv4.conf.eno1.rp_filter=0

配置 nftables 转发放行与状态追踪

要允许已经建立连接的回程包返回 br0,需要修改nft:

table inet filter {
    chain forward {
        type filter hook forward priority filter; policy drop;
        
        # 放行已建立连接的回程数据包
        ct state { established, related } accept
        
        # 放行从网桥发起的出站流量
        iifname "br0" accept
        
        # 放行 Babel 网段内的互相访问 (按需收紧)
        ip6 saddr fdcc::/16 ip6 daddr fdcc::/16 accept
    }
}

尝试在 VM-A 中执行:

ip -6 addr add fdcc:1::2/64 dev eth0 # 用于虚拟机间互联
ip addr add 172.0.0.1/24 dev eth0
ip -4 route add default via inet6 fdcc:1::1 dev eth0

此时理应能ping通公网IP和任意宿主机的组网IP。

B中类似,然后它俩预期就能相互通过组网内的地址互相连接了。

keywords