Build the lab yourself
The lab is one VyOS VM and five small Debian containers. It needs about 2 GB of free memory for Docker.
- Get the VyOS image as in the tcpdump lesson (
get-vyos.sh), into./vyos. - Put
lab-compose.yaml,start-vyos.shandhosts-setup.shin the same directory, thendocker compose up -d --buildand./hosts-setup.sh. - Wait a few minutes for VyOS to boot, then attach to its console (the command is at the top of
lab-compose.yaml) and log in asvyos/vyos. - Paste the stages from the lesson in order, with
configurebefore andcommitafter each: the interfaces, the input filter, the forward filter, then IPv6. - Run the tests from each section.
start-vyos.sh caps QEMU's translation cache with tb-size=256. QEMU's default is 1 GB on top of the VM's own memory, and in this lab that was enough for Docker Desktop to kill the VM for running out of memory.
# Needs ./vyos (from the tcpdump-and-vyos lesson's get-vyos.sh). Then: docker compose up -d --build && ./hosts-setup.sh
# VyOS console: docker compose exec -it r1 socat -,raw,echo=0,escape=0x1d UNIX-CONNECT:/run/console.sock
name: vfw-lab
x-host: &host
build:
dockerfile_inline: |
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
iproute2 iputils-ping curl tcpdump netcat-openbsd openssh-client python3 procps
CMD ["sleep", "infinity"]
image: ttr-vfw-host
privileged: true
services:
r1:
build:
context: .
dockerfile_inline: |
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends qemu-system-x86 iproute2 socat
COPY start-vyos.sh /usr/local/bin/start-vyos.sh
CMD ["/usr/local/bin/start-vyos.sh"]
cap_add: [NET_ADMIN]
devices: ["/dev/net/tun:/dev/net/tun"]
environment: { VYOS_MEM: "1536", VYOS_SMP: "1", VM_ID: "11" }
volumes: ["./vyos:/vyos:ro"]
networks: { wan: { interface_name: eth0 }, lan: { interface_name: eth1 }, mgmt: { interface_name: eth2 } }
isp: { <<: *host, hostname: isp, networks: { wan: { interface_name: eth0, priority: 100 }, internet: { interface_name: eth1 } } }
home: { <<: *host, hostname: home, networks: { internet: { interface_name: eth0 } } }
scanner: { <<: *host, hostname: scanner, networks: { internet: { interface_name: eth0 } } }
lanhost: { <<: *host, hostname: lanhost, networks: { lan: { interface_name: eth0 } } }
admin: { <<: *host, hostname: admin, networks: { mgmt: { interface_name: eth0 } } }
# Layer-2-only networks: every address comes from VyOS's config and hosts-setup.sh.
networks:
wan: { enable_ipv4: false, enable_ipv6: true, ipam: { config: [{ subnet: "fd00:b1::/64" }] } }
lan: { enable_ipv4: false, enable_ipv6: true, ipam: { config: [{ subnet: "fd00:b2::/64" }] } }
mgmt: { enable_ipv4: false, enable_ipv6: true, ipam: { config: [{ subnet: "fd00:b3::/64" }] } }
internet: { enable_ipv4: false, enable_ipv6: true, ipam: { config: [{ subnet: "fd00:b4::/64" }] } }- Lines 14–26: R1: a VyOS VM in QEMU, with one network per interface.
- Lines 27–31: Five small Debian hosts.
#!/bin/sh
# Bridge every container interface (eth0, eth1, …) to a tap, then boot VyOS with one virtio NIC per tap.
# VM NIC n gets MAC 52:54:00:<VM_ID>:0n:01, so VyOS's ethN matches the container's ethN. Give each VM its own VM_ID.
set -e
NETDEVS=""
n=0
for ifc in $(ls /sys/class/net | grep -E '^eth[0-9]+$' | sort -V); do
ip link add "br$n" type bridge
ip link set "$ifc" master "br$n"
ip tuntap add "tap$n" mode tap
ip link set "tap$n" master "br$n"
ip link set "tap$n" up
ip link set "br$n" up
# Pin the VM's MAC to its tap and stop learning on the Docker side: Docker reflects the VM's
# broadcasts back, which would otherwise teach the bridge the VM's MAC on the wrong port.
bridge link set dev "$ifc" learning off
bridge fdb replace "52:54:00:${VM_ID:-00}:0$n:01" dev "tap$n" master static
NETDEVS="$NETDEVS -netdev tap,id=n$n,ifname=tap$n,script=no,downscript=no -device virtio-net-pci,netdev=n$n,mac=52:54:00:${VM_ID:-00}:0$n:01"
n=$((n + 1))
done
# VYOS_TB_MB caps QEMU's TCG translation cache (QEMU's default is 1 GB on top of the guest RAM).
exec qemu-system-x86_64 -m "${VYOS_MEM:-2048}" -smp "${VYOS_SMP:-2}" -cpu max -accel tcg,thread=multi,tb-size=${VYOS_TB_MB:-256} \
-kernel /vyos/vmlinuz -initrd /vyos/initrd.img \
-append "boot=live components hostname=vyos username=live nopersistence noautologin nonetworking union=overlay console=ttyS0,115200 net.ifnames=0 biosdevname=0" \
-cdrom /vyos/vyos.iso $NETDEVS \
-display none -serial unix:/run/console.sock,server=on,wait=off -monitor none- Lines 21–22: Caps QEMU's translation cache, so the VM fits beside other containers.
#!/bin/sh
# Address the Debian hosts. IPv4 plus IPv6 from documentation ranges; Docker's own ULA addresses are removed.
cd "$(dirname "$0")"
x() { docker compose exec -T "$@"; }
for c in isp home scanner lanhost admin; do
x $c sh -c 'for i in $(ls /sys/class/net | grep ^eth); do ip -6 addr flush dev $i scope global; done'
done
x isp sh -c 'sysctl -qw net.ipv4.ip_forward=1 net.ipv6.conf.all.forwarding=1
ip addr add 203.0.113.1/30 dev eth0; ip -6 addr add 2001:db8:ffff::1/64 dev eth0
ip addr add 192.0.2.1/24 dev eth1; ip -6 addr add 2001:db8:f000::1/64 dev eth1
ip route add 198.51.100.0/24 via 203.0.113.2; ip -6 route add 2001:db8:10::/64 via 2001:db8:ffff::2; ip -6 route del default'
x home sh -c 'ip addr add 192.0.2.10/24 dev eth0; ip route add default via 192.0.2.1
ip -6 addr add 2001:db8:f000::10/64 dev eth0; ip -6 route replace default via 2001:db8:f000::1'
x scanner sh -c 'ip addr add 192.0.2.66/24 dev eth0; ip route add default via 192.0.2.1'
x lanhost sh -c 'ip addr add 198.51.100.10/24 dev eth0; ip route add default via 198.51.100.1
ip -6 addr add 2001:db8:10::10/64 dev eth0; ip -6 route replace default via 2001:db8:10::1'
x admin sh -c 'ip addr add 10.99.0.10/24 dev eth0; ip route add default via 10.99.0.1'
# A web server on the LAN host and on home, each logging who connected.
for c in lanhost home; do x -d $c sh -c "cd /tmp && echo 'hello from $c' > index.html && exec python3 -m http.server --bind :: 80 > /tmp/http.log 2>&1"; done
echo hosts readyWhere next
- Setting up VyOS with NATPrerequisite · Beginner · 45 min
A VyOS router with a NAT interface for a private LAN and a public interface for a routed public subnet: plain routing, source NAT, port forwarding and the forward firewall that NAT isn't. Every output comes from a real VyOS lab.
- IPv6 on VyOS: setup and routingAlso covers firewall, IPv6 · Beginner · 55 min
Number a site from an ISP's /48 on VyOS: WAN and blackhole routes, SLAAC with Router Advertisements, handing a /56 to a downstream router statically and with DHCPv6 prefix delegation, and the forward firewall IPv6 needs because there's no NAT. Every output comes from a real two-router VyOS lab.
- nftables and iptables on Debian: host and router firewallsAlso covers firewall, nftables · Intermediate · 60 min
Build a Debian firewall with nftables, from a host firewall to an office router with NAT and port forwards: safe changes with a timed rollback, sets and maps, logging and packet tracing, and what iptables really is on Debian 13.
- Zone-based firewall on VyOSAlso covers firewall · Intermediate · 35 min
Rebuild the NAT lesson's router with a zone-based firewall: WAN, LAN, a DMZ and the router itself, with one ruleset per direction. Includes the missing-return-traffic mistake, post-NAT matching, and a full test matrix.