+++ title = "Template for jail with an external IP assigned via DHCP" author = ["MichaƂ Sapka"] date = 2024-10-29T22:03:00+01:00 categories = ["bsd"] draft = false weight = 3001 primary_menu = "bsd" image_dir = "bsd" image_max_width = 600 abstract = "Running old adventure games" [menu] [menu.bsd] weight = 3001 identifier = "template-for-jail-with-an-external-ip-assigned-via-dhcp" parent = "freebsd-homelab" +++ The idea behind FreeBSD homelab is simple: to utilize the Jail system. Jails are great! What I want is to have jails with: - dedicated, external IP - IPs are assigned via DHCP server - I am able to access files outside if the jails I was able to achieve most of this by following [FreeBSD handbook](https://docs.freebsd.org/en/books/handbook/jails/), [Rubenerd's post](https://rubenerd.com/starting-with-freebsd-jails/), and [FreeBSD Wiki](https://wiki.freebsd.org/Jails), but I also received some help from different individuals whose names I can't recall. I use classic jails created from ZFS snapshots, but (as to the best of my knowledge), any jail will work with the following configuration. Unless specified, all code here goes to `/etc/jails.conf`. ```shell config1; jail1 { config2; } jail2 { config3; } ``` jail1 gets configured with `config1` and `config2`, while jail2 gets `config1` and `config3`. ## Jail configuration {#jail-configuration} First, we start with standard configuration regarding starting, stopping and logging. Notice the `#{name}`. It's a variable which fill be filled with the name of the jail. ```shell # STARTUP/LOGGING exec.clean; exec.start = "/bin/sh /etc/rc"; exec.stop = "/bin/sh /etc/rc.shutdown"; exec.consolelog = "/var/log/jail_console_${name}.log"; ``` Then we add permissions which will enable `vnet` - the system allowing for jail to have their own, (virtual) network stack. Even though everything goes through host's network stack, for all intends and purposes we can pretend that each jail has it's own (virtual) NIC. ```shell # PERMISSIONS allow.raw_sockets; exec.clean; mount.devfs; devfs_ruleset = 5; vnet; ``` Note, that we need to configure this `devfs_ruleset`. Create `etc/defvs.rules`: ```shell [devfsrules_jails=5] add include $devfsrules_hide_all add include $devfsrules_unhide_basic add include $devfsrules_unhide_login add path 'bpf*' unhide ``` back to `jail.conf`, we set hostname and path for the container. Adjust to your liking. ```shell host.hostname = "${name}.dune.local"; path = "/usr/local/jails/containers/${name}"; ``` Now for the actual network configuration. We will configure for the jail system to: - create an `epair(4)` and use for network communication - destroy this `epair` upon stopping a jail ```shell $epair = "epair${id}"; $bridge = "bridge0"; vnet.interface = "${epair}b"; exec.start += "dhclient ${epair}b"; 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.prestart += "/sbin/ifconfig ${epair}b ether ${mac}"; exec.poststop = "/sbin/ifconfig ${bridge} deletem ${epair}a"; exec.poststop += "/sbin/ifconfig ${epair}a destroy"; ``` For this to work, we need to create a `if_bridge(4)` on our host machine. Make sure that your `rc.conf` has: ```shell cloned_interfaces="bridge0" ifconfig_bridge0="addm em0 up" ``` (replace `em0` with appropriate device) Ok, now we just need to have our jail ready. First, create it as it presented in the [FreeBSD handbook](https://docs.freebsd.org/en/books/handbook/jails/). Then, configure the jail: ```shell jail { $id=1; $mac="2:bf:b9:4c:4f:0b"; exec.prestart += "mount -a -F /etc/fstab.$name"; exec.poststop += "umount -a -F /etc/fstab.$name"; } ``` Explanations: - `$id` will be used when creating matching `epair` - `$mac` will force a given mac address for the virtual network card. This will ensure that FreeBSD won't change it, and we can assign fixed `IP` on the router level - This jail has attached network storage. You don't want the jail itself to even know what it is, so we're forcing the host to execute `/etc/fstab.$name`, and mount the shares. Note, that you need to mount the drives in directory relative to **host's** root, so something like: ```shell 10.0.1.200:/volume2/movies /usr/local/jails/containers/servarr/mnt/movies nfs rw 0 0 ``` (this attaches an NFS share in read-write mode) ## Putting it all together {#putting-it-all-together} The entire `jail.conf` looks like: ```shell exec.clean; exec.start = "/bin/sh /etc/rc"; exec.stop = "/bin/sh /etc/rc.shutdown"; exec.consolelog = "/var/log/jail_console_${name}.log"; allow.raw_sockets; exec.clean; mount.devfs; devfs_ruleset = 5; vnet; host.hostname = "${name}.dune.local"; path = "/usr/local/jails/containers/${name}"; $epair = "epair${id}"; $bridge = "bridge0"; vnet.interface = "${epair}b"; exec.start += "dhclient ${epair}b"; 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.prestart += "/sbin/ifconfig ${epair}b ether ${mac}"; exec.poststop = "/sbin/ifconfig ${bridge} deletem ${epair}a"; exec.poststop += "/sbin/ifconfig ${epair}a destroy"; jail { $id=1; $mac="2:bf:b9:4c:4f:0b"; exec.prestart += "mount -a -F /etc/fstab.$name"; exec.poststop += "umount -a -F /etc/fstab.$name"; } ``` ## Improve me {#improve-me} Is this perfect? No! There are people doing magical things with jails. If this makes no sense to you, or if you've done it better - make sure to [contact me](/contact).