TTR Networks

Beginner · IPv6 on VyOS: setup and routing · section 8 of 8

Build the lab yourself

✓ Lab-tested 17 October 2026 on VyOS 2026.10.16-0024-rolling

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.

lab-compose.yaml
# 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.
start-vyos.sh
#!/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.
hosts-setup.sh
#!/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.