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
ifconfig_ue0="up"
Brings the physical interface up without assigning it an IP address.cloned_interfaces="bridge0"
Creates the bridge interface at boot so vnet interfaces have a place to attach.ifconfig_bridge0="up addm ue0 SYNCDHCP"
Brings the bridge up, addsue0as a member, and runs DHCP. The IP is assigned tobridge0rather than the physical interface.
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
exec.prestart = "ifconfig epair${id} create"
Creates the epair device used for the jail's vnet interface.exec.prestart += "ifconfig bridge0 addm epair${id}a"
Adds the host side of the epair tobridge0for LAN access.vnet;andvnet.interface = "epair${id}b"
Enables vnet and assigns the jail side of the epair to the jail.mount.devfs;anddevfs_ruleset = 5;
Mounts devfs inside the jail with the specified ruleset.persist;
Keeps the jail running even if it has no processes.
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.