> For the complete documentation index, see [llms.txt](https://handbook.bsdcn.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://handbook.bsdcn.org/di-17-zhang-jail-yu-rong-qi/17.6.-vnet-jail.md).

# 17.6.VNET jail

FreeBSD 的 VNET jail 拥有独立的网络栈，包括接口、IP 地址、路由表和防火墙规则。

创建 VNET jail 的第一步是创建 [bridge(4)](https://man.freebsd.org/cgi/man.cgi?query=bridge\&sektion=4\&format=html)，执行以下命令：

```sh
# ifconfig bridge create
```

输出应该类似于以下内容：

```sh
bridge0
```

> **警告**
>
> 将 IP 地址分配给作为网桥成员的接口已被弃用。应将宿主地址分配给 `bridge0` 本身，而非 `em0`，正如下面的 **/etc/rc.conf** 配置块所做的那样。此类配置目前仍然可用：FreeBSD 15.0 在将携带地址的接口添加到网桥时会记录内核警告，只有当 `net.link.bridge.member_ifaddrs` sysctl 从默认值 `1` 改为 `0` 时才会以 `EINVAL` 拒绝。[bridge(4)](https://man.freebsd.org/cgi/man.cgi?query=bridge\&sektion=4\&format=html) 宣布在 FreeBSD 16.0 中移除该 sysctl，届时网桥成员上的地址将不再被允许。

创建好 `bridge` 后，接下来需要将其附加到 `em0` 接口，并启动它们，执行以下命令：

```sh
# ifconfig bridge0 addm em0 up
# ifconfig em0 up
```

为了使此设置在重启后生效，将以下行添加到 **/etc/rc.conf** 中：

```sh
defaultrouter="192.168.1.1"
cloned_interfaces="bridge0"
ifconfig_bridge0="inet 192.168.1.150/24 addm em0 up"
ifconfig_em0="up"
```

有关桥接的更多信息，请参阅 [网络桥接](/di-35-zhang-gao-ji-wang-luo/35.8.-qiao-jie.md)。

接下来，根据上述内容创建 jail。

可使用 [传统 jail（厚 jail）](/di-17-zhang-jail-yu-rong-qi/17.4.-chuan-tong-jail-hou-jail.md) 或 [瘦 jail](/di-17-zhang-jail-yu-rong-qi/17.5.-shou-jail.md) 的方法。唯一不同的是 **/etc/jail.conf** 文件中的配置。

在本示例中，将使用路径 **/usr/local/jails/containers/vnet** 来表示创建的 jail。

以下是 VNET jail 的示例配置：

```sh
vnet {
  # 启动/日志记录
  exec.consolelog = "/var/log/jail_console_${name}.log";

  # 权限
  allow.raw_sockets;
  exec.clean;
  mount.devfs;
  devfs_ruleset = 5;

  # 路径/主机名
  path = "/usr/local/jails/containers/${name}";
  host.hostname = "${name}";

  # VNET/VIMAGE
  vnet;
  vnet.interface = "${epair}b";

  # 网络/接口
  $id = "154"; ①
  $ip = "192.168.1.${id}/24";
  $gateway = "192.168.1.1";
  $bridge = "bridge0"; ②
  $epair = "epair${id}";

  # 添加到 bridge 接口
  exec.prestart  = "/sbin/ifconfig ${epair} create up";
  exec.prestart += "/sbin/ifconfig ${epair}a up descr jail:${name}";
  exec.prestart += "/sbin/ifconfig ${bridge} addm ${epair}a up";
  exec.start    += "/sbin/ifconfig ${epair}b ${ip} up";
  exec.start    += "/sbin/route add default ${gateway}";
  exec.start    += "/bin/sh /etc/rc";
  exec.stop      = "/bin/sh /etc/rc.shutdown";
  exec.poststop = "/sbin/ifconfig ${bridge} deletem ${epair}a";
  exec.poststop += "/sbin/ifconfig ${epair}a destroy";
}
```

* ① 每个 jail **唯一** 的编号，用于派生 epair 设备名（`epair${id}`）和 jail 地址的最后一个八位组（`192.168.1.${id}`）；为网桥上的每个 jail 分配不同的值。
* ② 指代先前创建的 bridge。

`vnet.interface` 参数指定 [jail(8)](https://man.freebsd.org/cgi/man.cgi?query=jail\&sektion=8\&format=html) 在 jail 创建后移入 jail 网络栈的接口，此处为 epair 的 jail 端，即 `${epair}b`。该接口在 jail 运行时会从宿主消失，并在 jail 停止时自动释放回宿主。物理网卡或 SR-IOV 虚拟功能也可以同样的方式交给 jail，使其无需网桥或 epair 即可获得专用网络。

`devfs_ruleset = 5` 行应用 **/etc/defaults/devfs.rules** 中的 `devfsrules_jail_vnet` 规则集，该规则集通过暴露 **/dev/pf** 扩展了默认的 jail 规则集。因此，VNET jail 可使用 `pf_enable="YES"` 和私有的 **pf.conf** 运行自己的 [pf(4)](https://man.freebsd.org/cgi/man.cgi?query=pf\&sektion=4\&format=html) 防火墙，独立于宿主。关于规则集的定义方式请参见 [devfs 规则集](/di-17-zhang-jail-yu-rong-qi/17.3.-zhu-ji-pei-zhi-jail.md)，关于 pf 本身请参见 [防火墙](/di-34-zhang-fang-huo-qiang/34.1.-gai-shu.md) 一章。

## 17.6.1. 使用 jib 自动化 VNET 网络

如上所示，在 `exec.prestart` 和 `exec.poststop` 中手动连接 epair 并将其附加到网桥，在多个 jail 中会变得繁琐。基本系统附带了辅助脚本 **/usr/share/examples/jails/jib**，可自动执行相同的管道工作。

在 jail 的钩子中引用该脚本，而非五行手写的 [ifconfig(8)](https://man.freebsd.org/cgi/man.cgi?query=ifconfig\&sektion=8\&format=html) 命令：

```sh
  exec.prestart += "jib addm ${name} em0";
  exec.poststop += "jib destroy ${name}";

  vnet.interface = "e0b_${name}";
```

`jib addm` 创建一个 epair，其宿主端命名为 `e0a_<name>`，jail 端命名为 `e0b_<name>`，然后将宿主端附加到它自动创建的网桥上，该网桥以成员接口命名（对于 `em0`，网桥名为 `em0bridge`）。在 `vnet.interface` 中指定该 jail 端。当 jail 停止时，`jib destroy` 会移除该 epair。该脚本还提供了 `jib show` 子命令来列出其管理的接口。

## 17.6.2. 使用 jng 的 netgraph VNET 网络

[网络](/di-17-zhang-jail-yu-rong-qi/17.3.-zhu-ji-pei-zhi-jail.md) 概述中提到的第三种网络模式使用 [netgraph(4)](https://man.freebsd.org/cgi/man.cgi?query=netgraph\&sektion=4\&format=html) 而非 [bridge(4)](https://man.freebsd.org/cgi/man.cgi?query=bridge\&sektion=4\&format=html) 和 epair。基本系统附带了第二个辅助脚本 **/usr/share/examples/jails/jng**，可为每个 jail 构建一个连接到 `ng_eiface` 的 `ng_bridge`：

```sh
  exec.prestart += "jng bridge ${name} em0";
  exec.poststop += "jng shutdown ${name}";

  vnet.interface = "ng0_${name}";
```

`jng bridge` 创建 netgraph 节点和一个名为 `ng0_<name>` 的 jail 端接口，后者在 `vnet.interface` 中指定；`jng shutdown` 在 jail 停止时拆除该拓扑。结果在功能上等同于 epair-and-bridge 设置，但构建在 netgraph 节点而非 [bridge(4)](https://man.freebsd.org/cgi/man.cgi?query=bridge\&sektion=4\&format=html) 之上。

## 17.6.3. 在 VNET jail 中运行防火墙和 DHCP

由于 `devfs_ruleset = 5` 暴露了 **/dev/pf**，VNET jail 可运行自己的防火墙。在 jail 内启用它，方式与任何宿主相同，即在 jail 的 **/etc/rc.conf** 中设置 `pf_enable="YES"`，并使用自己的 **/etc/pf.conf**。

但规则集 5 不暴露 **/dev/bpf**。通过 DHCP 获取地址的 VNET jail（例如在其 **/etc/rc.conf** 中设置 `ifconfig_e0b_myjail="SYNCDHCP"`）会运行 [dhclient(8)](https://man.freebsd.org/cgi/man.cgi?query=dhclient\&sektion=8\&format=html)，该程序需要 bpf，因此在规则集 5 下会失败。按照 [devfs 规则集](/di-17-zhang-jail-yu-rong-qi/17.3.-zhu-ji-pei-zhi-jail.md) 中的方法，在 **/etc/devfs.rules** 中定义一个自定义规则集，从 `devfsrules_jail_vnet` 开始，并额外取消隐藏 bpf 设备：

```sh
[devfsrules_jail_vnet_dhcp=101]
add include $devfsrules_jail_vnet
add path 'bpf*' unhide
```

在宿主上运行 `service devfs restart` 后，使用 `devfs_ruleset = 101;` 将 jail 指向新规则集。
