About

This post documents how to build a "thick" FreeBSD jail, sometimes also known as a classic jail.

Network

Overview

Before we start it is important that our host network stack is prepared for vnet jails. A vnet jail has its own network stack, its own IP address, and its own network interfaces. Those interfaces typically join a bridge on the host so the jail can reach the outside world.

Here is the host rc.conf setup I use. The ue0 interface is the physical interface on my machine (a USB adapter):

ifconfig_ue0="up"
cloned_interfaces="bridge0"
ifconfig_bridge0="up addm ue0 SYNCDHCP"

Explanation

Note
In a typical LAN setup, jails can obtain IPs automatically via DHCP once their vnet interfaces are attached to the bridge. Jails can use ifconfig_DEFAULT="SYNCDHCP" to request a lease from their router on boot.

Bootstrap

/tmp/jail

For the purpose of this post we will store our jail in /tmp/jail. This is just to show that building a jail can be fast even with base tools, and you don't need to over engineer the problem at first:

mkdir -p /tmp/jail

/usr/src

The next step is to populate our jail with the FreeBSD base system. We're going to do this through a copy of /usr/src that should at least build world. This is also a good time to modify the jail filesystem if you want to. For example we could add /etc/rc.conf, /etc/rc.local, and other configuration files before we boot the jail:

cd /usr/src
make buildworld
DESTDIR=/tmp/jail make installworld distribution

jail.conf

The following configuration we're going to embed directly into /etc/jail.conf for simplicity. It is also possible to break them up into separate files, and if you prefer that setup, feel free to do that instead. This will be our configuration, and I'm going to defer to jail(8) when it comes offering an in-depth explanation of the various parameters:

tmp {
  $domain="tmp";
  $id="99";

  jid = "${id}";
  host.hostname = "${domain}.local";
  path = "/tmp/jail";
  exec.start = "/bin/sh /etc/rc";
  exec.stop = "/bin/sh /etc/rc.shutdown";

  exec.prestart = "ifconfig epair${id} create";
  exec.prestart += "ifconfig epair${id}a up";
  exec.prestart += "ifconfig bridge0 addm epair${id}a";
  exec.poststop = "ifconfig bridge0 deletem epair${id}a";
  exec.poststop += "ifconfig epair${id}a destroy";

  vnet;
  vnet.interface = "epair${id}b";

  mount.devfs;
  devfs_ruleset = 5;

  persist;
}

Explanation

jail(8)

We're finally in a position where we can start the jail. That can be done with jail -c tmp, and then we should see whether or not the jail booted successfully. We can remove the jail with jail -r tmp when we're done, and the jls(8) utility is also quite useful and can do a bit more than just list jails. Maybe for a future post.