I set up a FreeBSD VNET jail named jailbox to run qBittorrent through an OpenVPN tunnel to a seedbox VPN provider. The goal was:
- Use a thick FreeBSD jail.
- Keep the jail on DHCP like my other VNET jails.
- Run OpenVPN inside the jail.
- Run qBittorrent-nox inside the jail.
- Force torrent traffic through the VPN.
- Use a host-side PF kill switch so the jail cannot leak traffic through the normal WAN if the VPN drops.
- Keep the Web UI reachable from the LAN.
Code: Select all
FreeBSD host
-> bridge0
-> PF enabled
-> bridge member filtering enabled
-> epair host-side interface for jailbox
-> jailbox VNET jail
-> DHCP LAN address
-> OpenVPN client to seedbox server
-> tun0 VPN interface
-> qBittorrent-nox
-> Web UI on port 8080
-> torrent listener bound to tun0
1. Create the thick jail
Create the jail:
Code: Select all
bsdinstall jail /usr/local/jails/jailbox
Code: Select all
cp /etc/resolv.conf /usr/local/jails/jailbox/etc/resolv.conf
Edit:
Code: Select all
/etc/jail.conf
Code: Select all
jailbox {
host.hostname = "jailbox";
path = "/usr/local/jails/jailbox";
vnet;
vnet.interface = "jailbox0";
exec.clean;
exec.system_user = "root";
exec.jail_user = "root";
exec.prestart = "ifconfig epair14 create";
exec.prestart += "ifconfig epair14a descr jailbox-host";
exec.prestart += "ifconfig epair14a up";
exec.prestart += "ifconfig bridge0 addm epair14a";
exec.prestart += "ifconfig epair14b name jailbox0";
exec.start = "/bin/sh /etc/rc";
exec.stop = "/bin/sh /etc/rc.shutdown";
exec.poststop = "ifconfig bridge0 deletem epair14a";
exec.poststop += "ifconfig epair14a destroy";
mount.devfs;
devfs_ruleset = 14;
persist;
allow.raw_sockets = 0;
allow.set_hostname = 0;
allow.sysvipc = 0;
allow.mount = 0;
allow.mount.devfs = 0;
allow.mount.nullfs = 0;
allow.mount.procfs = 0;
allow.mount.tmpfs = 0;
allow.chflags = 0;
enforce_statfs = 2;
children.max = 0;
}
Code: Select all
epair14a = host side
epair14b = renamed to jailbox0 inside the jail
3. Configure devfs rules for DHCP and OpenVPN
Because the jail uses DHCP, it needs bpf. Because OpenVPN runs inside the jail, it needs tun.
Edit:
Code: Select all
/etc/devfs.rules
Code: Select all
[devfsrules_jailbox=14]
add include $devfsrules_jail
# Needed for DHCP inside the VNET jail.
add path 'bpf*' unhide
# Needed for OpenVPN inside the jail.
add path 'tun' unhide
add path 'tun*' unhide
Code: Select all
service devfs restart
Edit:
Code: Select all
/usr/local/jails/jailbox/etc/rc.conf
Code: Select all
hostname="jailbox"
ifconfig_jailbox0="SYNCDHCP"
sshd_enable="NO"
sendmail_enable="NONE"
syslogd_flags="-ss"
clear_tmp_enable="YES"
dumpdev="NO"
moused_nondefault_enable="NO"
Code: Select all
sysrc jail_list+=" jailbox"
Code: Select all
service jail start jailbox
Code: Select all
jls
jexec jailbox hostname
jexec jailbox ifconfig jailbox0
jexec jailbox ls -l /dev/bpf*
On FreeBSD 15.x, the TUN/TAP driver is usually if_tuntap.ko.
Check for it:
Code: Select all
ls /boot/kernel/if_tuntap.ko
Code: Select all
kldload if_tuntap
Code: Select all
module already loaded or in kernel
Test tun creation on the host:
Code: Select all
ifconfig tun0 create
ifconfig tun0
ls -l /dev/tun*
ifconfig tun0 destroy
Code: Select all
jexec jailbox ifconfig tun0 create
jexec jailbox ifconfig tun0
jexec jailbox ls -l /dev/tun*
jexec jailbox ifconfig tun0 destroy
6. Install OpenVPN inside jailbox
Enter the jail:
Code: Select all
jexec jailbox /bin/sh
Code: Select all
pkg update
pkg install openvpn ca_root_nss curl nano
Code: Select all
mkdir -p /usr/local/etc/openvpn
chmod 700 /usr/local/etc/openvpn
Code: Select all
exit
Example:
Code: Select all
cp /home/YOURUSER/Seedbox.ovpn /usr/local/jails/jailbox/usr/local/etc/openvpn/seedbox.conf
Code: Select all
jexec jailbox chmod 600 /usr/local/etc/openvpn/seedbox.conf
Enter the jail:
Code: Select all
jexec jailbox /bin/sh
Code: Select all
cat > /usr/local/etc/openvpn/seedbox.auth <<'EOF'
SEEDBOX_USERNAME
SEEDBOX_PASSWORD
EOF
chmod 600 /usr/local/etc/openvpn/seedbox.auth
Code: Select all
nano /usr/local/etc/openvpn/seedbox.conf
Code: Select all
auth-user-pass
Code: Select all
auth-user-pass /usr/local/etc/openvpn/seedbox.auth
Code: Select all
proto udp
Code: Select all
proto udp4
Code: Select all
remote SEEDBOX_SERVER_HOSTNAME 1194
Code: Select all
remote SEEDBOX_SERVER_IP 1194
Code: Select all
auth-nocache
Code: Select all
# pull-filter ignore 'route '
# pull-filter ignore redirect-gateway
8. Enable and start OpenVPN
Inside or from the host:
Code: Select all
jexec jailbox sysrc openvpn_enable=YES
jexec jailbox sysrc openvpn_configfile="/usr/local/etc/openvpn/seedbox.conf"
Code: Select all
jexec jailbox service openvpn start
Code: Select all
jexec jailbox tail -50 /var/log/messages
Code: Select all
Initialization Sequence Completed
Code: Select all
jexec jailbox ifconfig tun0
Code: Select all
jexec jailbox netstat -rn
Code: Select all
0.0.0.0/1 via tun0
128.0.0.0/1 via tun0
SEEDBOX_SERVER via LAN gateway
Code: Select all
jexec jailbox curl -4 https://ifconfig.me
9. Set up PF kill switch on the host
The jail uses DHCP, so I did not target the jail IP address. Instead, I targeted the host-side epair interface:
Code: Select all
epair14a
Code: Select all
which pfctl
ls -l /etc/rc.d/pf
Code: Select all
kldload pf
Code: Select all
/etc/pf.conf
Code: Select all
#
# /etc/pf.conf
# Host-side PF rules for jailbox OpenVPN kill switch
#
jailbox_if = "epair14a"
lan_net = "192.168.11.0/24"
seedbox_vpn_ip = "SEEDBOX_SERVER_IP"
seedbox_vpn_port = "1194"
set skip on lo0
#
# Allow DHCP for jailbox.
#
pass in quick on $jailbox_if proto udp from port 68 to port 67 keep state
pass out quick on $jailbox_if proto udp from port 67 to port 68 keep state
#
# Allow jailbox DNS to LAN resolvers.
#
pass in quick on $jailbox_if proto { udp tcp } from any to $lan_net port 53 keep state
#
# Allow jailbox to connect directly only to the seedbox OpenVPN endpoint.
#
pass in quick on $jailbox_if proto udp from any to $seedbox_vpn_ip port $seedbox_vpn_port keep state
#
# Allow jailbox to talk to local LAN.
#
pass in quick on $jailbox_if from any to $lan_net keep state
#
# Allow LAN clients to reach services inside jailbox.
# This allows the qBittorrent Web UI.
#
pass out quick on $jailbox_if from $lan_net to any keep state
#
# Kill switch:
# Block all other direct non-VPN traffic from jailbox.
#
block in quick on $jailbox_if from any to any
Code: Select all
SEEDBOX_SERVER_IP
You can resolve the server hostname with:
Code: Select all
host SEEDBOX_SERVER_HOSTNAME
Code: Select all
pfctl -nf /etc/pf.conf
Code: Select all
sysrc pf_enable=YES
sysrc pf_rules="/etc/pf.conf"
service pf start
Code: Select all
pfctl -sr
Because the jail is connected through bridge0, PF needs to inspect traffic on the bridge member interface.
Enable live:
Code: Select all
sysctl net.link.bridge.pfil_member=1
sysctl net.link.bridge.pfil_bridge=0
Code: Select all
cat >> /etc/sysctl.conf <<'EOF'
# PF filtering for VNET jail bridge member interfaces
net.link.bridge.pfil_member=1
net.link.bridge.pfil_bridge=0
EOF
Code: Select all
grep -n 'net.link.bridge.pfil' /etc/sysctl.conf
With OpenVPN running:
Code: Select all
jexec jailbox curl -4 https://ifconfig.me
Code: Select all
SEEDBOX_PUBLIC_IP
Code: Select all
jexec jailbox service openvpn stop
Code: Select all
jexec jailbox curl -4 --connect-timeout 10 https://ifconfig.me
Code: Select all
curl: (28) Connection timed out
Code: Select all
jexec jailbox service openvpn start
sleep 5
jexec jailbox curl -4 https://ifconfig.me
Code: Select all
SEEDBOX_PUBLIC_IP
Code: Select all
pfctl -vvsr
Code: Select all
block drop in quick on epair14a all
Install:
Code: Select all
jexec jailbox pkg install qbittorrent-nox
Code: Select all
user: qbittorrent
group: qbittorrent
Code: Select all
jexec jailbox mkdir -p /srv/torrents/downloads
jexec jailbox mkdir -p /srv/torrents/incomplete
jexec jailbox mkdir -p /srv/torrents/watch
jexec jailbox mkdir -p /srv/torrents/completed
jexec jailbox chown -R qbittorrent:qbittorrent /srv/torrents
jexec jailbox chmod -R 775 /srv/torrents
Code: Select all
jexec jailbox sh -c 'ls -ld /srv/torrents /srv/torrents/*'
Check the rc script:
Code: Select all
jexec jailbox grep -n 'qbittorrent_' /usr/local/etc/rc.d/qbittorrent
Code: Select all
jexec jailbox mkdir -p /var/db/qbittorrent/conf
jexec jailbox chown -R qbittorrent:qbittorrent /var/db/qbittorrent
Code: Select all
jexec jailbox sysrc qbittorrent_enable=YES
jexec jailbox sysrc qbittorrent_user=qbittorrent
jexec jailbox sysrc qbittorrent_group=qbittorrent
jexec jailbox sysrc qbittorrent_conf_dir="/var/db/qbittorrent/conf"
jexec jailbox sysrc qbittorrent_download_dir="/srv/torrents/downloads"
jexec jailbox sysrc qbittorrent_flags="--confirm-legal-notice"
Code: Select all
jexec jailbox service qbittorrent start
Code: Select all
jexec jailbox service qbittorrent status
jexec jailbox pgrep -lf qbittorrent
jexec jailbox sockstat -4 -l | grep -i qbit
Code: Select all
*:8080
Code: Select all
http://jailbox:8080/
Code: Select all
http://JAILBOX_DHCP_IP:8080/
Default username is usually:
Code: Select all
admin
If you need to see the temporary password, stop the daemon and run qBittorrent in the foreground:
Code: Select all
jexec jailbox service qbittorrent stop
jexec jailbox su -m qbittorrent -c "qbittorrent-nox --profile=/var/db/qbittorrent/conf --save-path=/srv/torrents/downloads --confirm-legal-notice"
Then stop it with Ctrl+C and restart the service:
Code: Select all
jexec jailbox service qbittorrent start
Do not change the Web UI to port 80 unless you are using a reverse proxy. qBittorrent runs as the unprivileged qbittorrent user, so binding directly to port 80 can fail. Keep it on:
Code: Select all
8080
In the Web UI:
Connection
Code: Select all
Listening port: 51413
Use UPnP / NAT-PMP: disabled
Use different port on each startup: disabled
Code: Select all
Network interface: tun0
Downloads
Code: Select all
Default save path: /srv/torrents/downloads
Incomplete path: /srv/torrents/incomplete
Watched folder: /srv/torrents/watch
Run:
Code: Select all
jexec jailbox sockstat -4 -l | grep -i qbit
Code: Select all
qbittorrent ... tcp4 10.x.x.x:51413
qbittorrent ... udp4 10.x.x.x:51413
qbittorrent ... tcp46 *:8080
17. Final validation
With OpenVPN up:
Code: Select all
jexec jailbox curl -4 https://ifconfig.me
Code: Select all
SEEDBOX_PUBLIC_IP
Code: Select all
jexec jailbox service openvpn stop
Code: Select all
jexec jailbox curl -4 --connect-timeout 10 https://ifconfig.me
Code: Select all
curl: (28) Connection timed out
Code: Select all
jexec jailbox service openvpn start
sleep 5
jexec jailbox curl -4 https://ifconfig.me
Code: Select all
SEEDBOX_PUBLIC_IP
Code: Select all
FreeBSD host
PF loaded and enabled
/etc/pf.conf active
bridge member filtering enabled
epair14a used as jailbox kill-switch interface
jailbox
thick VNET jail
DHCP address from LAN
OpenVPN to seedbox server
tun0 active
qBittorrent-nox running as qbittorrent user
Web UI on port 8080
torrent listener bound to tun0
torrent storage in /srv/torrents
Code: Select all
OpenVPN up:
jailbox exits through the seedbox VPN
OpenVPN down:
jailbox cannot reach the internet
LAN access:
browser can still reach http://jailbox:8080/
Code: Select all
jexec jailbox service openvpn status
jexec jailbox service qbittorrent status
jexec jailbox curl -4 https://ifconfig.me
jexec jailbox sockstat -4 -l | grep -i qbit
pfctl -vvsr
Code: Select all
curl returns the seedbox VPN public IP
qBittorrent torrent port is on 10.x.x.x:51413
qBittorrent Web UI is on *:8080
PF block rule increments only when something tries to bypass the VPN