Build the lab yourself
Two VyOS VMs and four Linux containers. Put ./vyos (from the Packets That Never Come Back lesson's get-vyos.sh) next to these files, run docker compose up -d --build, then ./hosts-setup.sh. Log in on each VyOS console as vyos/vyos and apply the configuration blocks in the order the lesson shows them.
# 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: vyos-ipv6-lab
x-linux: &linux
build:
dockerfile_inline: |
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
iproute2 iputils-ping traceroute ndisc6 curl python3 procps
CMD ["sleep", "infinity"]
privileged: true
x-vyos: &vyos
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"]
volumes: ["./vyos:/vyos:ro"]
services:
r1:
<<: *vyos
environment: { VYOS_MEM: "1536", VYOS_SMP: "1", VM_ID: "01" }
networks: { wan: { interface_name: eth0 }, lan1: { interface_name: eth1 }, transit: { interface_name: eth2 } }
r2:
<<: *vyos
environment: { VYOS_MEM: "1536", VYOS_SMP: "1", VM_ID: "02" }
networks: { transit: { interface_name: eth0 }, lan2: { interface_name: eth1 } }
isp:
<<: *linux
networks: { wan: { interface_name: eth0 }, internet: { interface_name: eth1 } }
server:
<<: *linux
networks: { internet: { interface_name: eth0 } }
client1:
<<: *linux
networks: { lan1: { interface_name: eth0 } }
client2:
<<: *linux
networks: { lan2: { interface_name: eth0 } }
# IPv4-only Docker networks on purpose: Docker then adds no IPv6 addresses or routes,
# so every IPv6 address and route comes from VyOS and the configs. hosts-setup.sh removes the IPv4.
networks:
wan: { ipam: { config: [{ subnet: "172.31.50.0/24" }] } }
internet: { ipam: { config: [{ subnet: "172.31.51.0/24" }] } }
lan1: { ipam: { config: [{ subnet: "172.31.52.0/24" }] } }
lan2: { ipam: { config: [{ subnet: "172.31.53.0/24" }] } }
transit: { ipam: { config: [{ subnet: "172.31.54.0/24" }] } }- Lines 4–11: Linux toolbox with
ndisc6(for rdisc6) and traceroute. - Lines 24–31: Two VyOS VMs with unique IDs.
- Lines 44–51: IPv4-only Docker networks, so Docker adds no IPv6: everything IPv6 comes from VyOS.
#!/bin/sh
# Bridge every container interface (eth0, eth1, …) to a tap, then boot VyOS with one virtio NIC per tap.
# Each VM gets a fixed SMBIOS UUID: VyOS derives its DHCPv6 DUID from it, and without one DHCPv6 clients fail.
# 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
exec qemu-system-x86_64 -m "${VYOS_MEM:-2048}" -smp "${VYOS_SMP:-2}" -cpu max -accel tcg,thread=multi \
-uuid "00000000-0000-4000-8000-0000000000${VM_ID:-00}" \
-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- Line 3: Why each VM gets a UUID.
- Line 23: The UUID that VyOS turns into its DHCPv6 DUID.
#!/bin/sh
# IPv6-only Linux hosts: remove Docker's IPv4, enable IPv6, then static addresses for the ISP and server.
# The clients get nothing static: their addresses and default routes come from VyOS's Router Advertisements.
cd "$(dirname "$0")"
x() { docker compose exec -T "$@"; }
for c in isp server client1 client2; do
x $c sh -c 'for i in $(ls /sys/class/net | grep ^eth); do ip -4 addr flush dev $i; sysctl -qw net.ipv6.conf.$i.disable_ipv6=0; done; ip -4 route flush table main'
done
x isp sh -c 'sysctl -qw net.ipv6.conf.all.forwarding=1; ip -6 addr add 2001:db8:ffff:1::1/64 dev eth0; ip -6 addr add 2001:db8:f000::1/64 dev eth1; ip -6 route add 2001:db8:1000::/48 via 2001:db8:ffff:1::2'
x server sh -c 'ip -6 addr add 2001:db8:f000::10/64 dev eth0; ip -6 route add default via 2001:db8:f000::1'
for c in server client1 client2; 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 ready- Line 7: Remove Docker's IPv4 and enable IPv6 on every interface.
- Line 9: The ISP routes our /48 to R1.
- Line 10: The server, statically addressed.
- Line 11: Web servers that log who connected.
Where 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.
- IPv4 over an IPv6-only core (RFC 8950)Builds on this lesson · Intermediate · 40 min
Carry IPv4 routes over IPv6 BGP sessions with IPv6 next hops, so core routers need no IPv4 addresses at all. Built on VyOS: the missing router ID, what breaks without the extended next hop capability, native IPv4 on the wire, and why traceroute shows 192.0.0.8.
- 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.
- OSPF on VyOS: costs and OSPFv3Also covers IPv6 · Intermediate · 30 min
OSPF the VyOS way: interface-based configuration, reading the interface and database output, steering traffic with costs (and the asymmetric routing that follows), and OSPFv3 for IPv6 over link-local addresses. Captured on the three-router edge/core lab.