I’ve only just started using FreeBSD jails, so bear with me, but I believe this is workable, at least “it works on my machines”.

I suddenly noticed that iocage jails couldn’t “speak” to the Internets when they used private addresses. It took me a long time to realize it is because the PF firewall configuration I was using from a prior BastilleBSD experiment wasn’t getting the addresses of the iocage jails added to it.

ext_if="em0"

table <jails> persist
nat on $ext_if from <jails> to any -> ($ext_if)
...

I started mucking about with PF, but that’s not my department … and so the jails table remained empty which meant the jail could not access anything beyond the host.

After a bit of searching I found iocage supports most jail(8) parameters, so I did this:

iocage set exec_clean=0 default
iocage set exec_created="/usr/local/sbin/jpjail" default

exec.created contains command(s) to run in the system environment right after a jail has been created, and the disabled exec.clean causes iocage to pass a few useful environment variables into the command(s):

IOCAGE_DEBUG=FALSE
IOCAGE_FORCE=FALSE
IOCAGE_HOSTNAME=v11
IOCAGE_NAME=ioc-v11
IOCAGE_SKIP=FALSE

So, the rest was relatively easy and turned into this for jpjail:

#!/bin/sh

pfctl -t jails -T add $( jls -j "${IOCAGE_NAME}" ip4.addr )

The pfctl utility adds the current jail’s IPv4 address to the PF jails table. (I hope I’m saying this correctly.)

It turns out the environment variables are not passed down from an exec_prestop so I cannot use those remove the address from the table. I found that $name (which contains the value of $IOCAGE_NAME) is expanded to the name of the jail in an exec_clean invocation (which would save having to request environment variables by disabling exec_clean), but that $name this isn’t available in either of _prestop nor _poststop. Sighs.

Anyhow: it works.

I’m sure somebody with more firewall knowhow would have solved this in a more elegant fashion.

Updates

Bert Regeer proposes something much simpler which doesn’t require the exec complexity: Instead of <table> he adds a CIDR range:

nat on $ext_if from 192.168.0.1/24 to any -> ($ext_if)

Then all jails get an IP address in that 192.168.0.1-255 range.

Ollivier Robert does something similar on outbound NAT and shows me how he handles incoming UDP/TCP connections which are redirected to a jail:

ext_if = bce0
...
jail_ip_4       = 127.0.1.4
jail_ports_4    = "{ 53, 8053 }"

jail_ip_5       = 127.0.1.5
jail_ports_5    = "{ }"
...
rdr on $ext_if proto udp from any to $ext_if port $jail_ports_4 -> $jail_ip_4
rdr on $ext_if proto tcp from any to $ext_if port $jail_ports_5 -> $jail_ip_5
freebsd and jails :: 07 Dec 2019 :: e-mail