TTR Networks

Beginner · Setting up VyOS with NAT · section 7 of 7

Build the lab yourself

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

One VyOS VM 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 to address the Linux hosts and start their web servers. Log in on the VyOS console as vyos/vyos and apply the stages in order.

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-nat-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 curl tcpdump netcat-openbsd python3 procps
      CMD ["sleep", "infinity"]
  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"]
    volumes: ["./vyos:/vyos:ro"]
    networks: { wan: { interface_name: eth0 }, nat-lan: { interface_name: eth1 }, public-lan: { interface_name: eth2 } }
  isp:
    <<: *linux
    networks: { wan: { interface_name: eth0 }, internet: { interface_name: eth1 } }
  server:
    <<: *linux
    networks: { internet: { interface_name: eth0 } }
  lanhost:
    <<: *linux
    networks: { nat-lan: { interface_name: eth0 } }
  pubhost:
    <<: *linux
    networks: { public-lan: { interface_name: eth0 } }
networks:
  wan: { enable_ipv4: false, enable_ipv6: true, ipam: { config: [{ subnet: "fd00:40::/64" }] } }
  nat-lan: { enable_ipv4: false, enable_ipv6: true, ipam: { config: [{ subnet: "fd00:41::/64" }] } }
  public-lan: { enable_ipv4: false, enable_ipv6: true, ipam: { config: [{ subnet: "fd00:42::/64" }] } }
  internet: { enable_ipv4: false, enable_ipv6: true, ipam: { config: [{ subnet: "fd00:43::/64" }] } }
  • Lines 4–11: One toolbox image for all the Linux hosts.
  • Lines 13–24: The VyOS VM: WAN, NAT-LAN and public LAN, in that order.
  • Lines 25–27: The ISP is a plain Linux router between the WAN and the internet.
  • Lines 37–41: Layer-2-only networks: every IPv4 address comes from the configs.
hosts-setup.sh
#!/bin/sh
# Address the Linux containers and start a web server on each host that logs who connected.
cd "$(dirname "$0")"
x() { docker compose exec -T "$@"; }
for c in isp server lanhost pubhost; do x $c sh -c 'sysctl -qw net.ipv6.conf.all.disable_ipv6=1; ip -4 addr flush dev eth0; ip -4 addr flush dev eth1 2>/dev/null; true'; done
x isp sh -c 'ip addr add 203.0.113.1/30 dev eth0; ip addr add 192.0.2.1/24 dev eth1; sysctl -qw net.ipv4.ip_forward=1; ip route replace 198.51.100.0/28 via 203.0.113.2'
x server sh -c 'ip addr add 192.0.2.10/24 dev eth0; ip route replace default via 192.0.2.1'
x lanhost sh -c 'ip addr add 10.0.0.10/24 dev eth0; ip route replace default via 10.0.0.1'
x pubhost sh -c 'ip addr add 198.51.100.2/28 dev eth0; ip route replace default via 198.51.100.1'
for c in server lanhost pubhost; do x -d $c sh -c "cd /tmp && echo 'hello from $c' > index.html && exec python3 -m http.server 80 > /tmp/http.log 2>&1"; done
x -d pubhost sh -c "exec nc -lk 5432"   # a "database" that should never be reachable from the internet
echo hosts ready
  • Line 6: The ISP routes our public /28 to R1, and has no route to 10.0.0.0/8, just like the real internet.
  • Line 10: A web server on each host that logs every visitor's address.
  • Line 11: The "database" used in the port-forwarding and firewall tests.
start-vyos.sh
#!/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:00:0n:01, so VyOS's ethN matches the container's ethN.
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: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:00:0$n:01"
  n=$((n + 1))
done
exec qemu-system-x86_64 -m 2048 -smp 2 -cpu max -accel tcg,thread=multi \
  -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 14–17: Pin the VM's MAC to its tap and turn off learning on the Docker side.

Where next

  • Zone-based firewall on VyOSBuilds on this lesson · 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.

  • IPv6 on VyOS: setup and routingBuilds on this lesson · 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.

  • Office gateway: DHCP, DNS and VRRP on VyOSBuilds on this lesson · Beginner · 40 min

    Two VyOS routers serving one office LAN: VRRP for a gateway that survives a router failure, DHCP with split pools and a printer reservation, and DNS forwarding. Then a real failover, the DNS outage it exposed, and the fix.

  • Site-to-site VPN with WireGuard on VyOSBuilds on this lesson · Intermediate · 35 min

    Join two sites' private LANs across the internet with WireGuard on VyOS: keys and peers, the AllowedIPs mistake and what it teaches, what the internet actually sees, and the tunnel's smaller MTU.