Build the lab yourself
The lab runs on any machine with Docker. The three routers use under 100 MB of memory between them (about 22 MB each here) and no virtual machines, because FRR runs happily in containers. Every file you need is on this page.
Put the files in one directory: the compose file, the Dockerfile, the daemons file and the three router configs from earlier in the lesson (r1.conf, r2.conf and r3.conf). Then:
docker compose -f lab-compose.yaml up -d --build
docker compose -f lab-compose.yaml exec r1 vtysh
Within about 20 seconds show bgp summary on r1 should show r2 established with 4 prefixes received and 1 sent.
Breaking it
Each scenario in this lesson is one or two changes. Make them in vtysh (configure terminal), watch, then put things back. docker compose -f lab-compose.yaml up -d --force-recreate returns all three routers to the files' configuration in a few seconds.
| Scenario | Change |
|---|---|
| Admin shutdown | r1: router bgp 64500 → neighbor 192.0.2.1 shutdown |
| Max-prefix | r1: address-family ipv4 unicast → neighbor 192.0.2.1 maximum-prefix 3 |
| Wrong address | r1: neighbor 192.0.2.9 remote-as 64501 |
| Peer not configured | r2: router bgp 64501 → no neighbor 192.0.2.0 |
| Firewall | r2 shell: iptables -A INPUT -i eth0 -p tcp --dport 179 -j DROP and the same with --sport 179 |
| Wrong remote AS | r1: neighbor 192.0.2.1 remote-as 64999 |
| Loopbacks without multihop | r1: neighbor 198.51.100.243 remote-as 64501, … update-source lo; r3: the same towards 198.51.100.241 with remote-as 64500 |
| Hold timer | both ends: neighbor … timers 5 15, clear, then drop all traffic between r1 and r2 with iptables on r2 |
| Missing policy | r1: address-family ipv4 unicast → no neighbor 192.0.2.1 route-map FROM-R2 in and … TO-R2 out |
| Import filter | r1: ip prefix-list FROM-R2 seq 5 deny 198.51.100.64/26, then clear bgp 192.0.2.1 soft in |
| Unreachable next hop | r2: address-family ipv4 unicast → no neighbor 192.0.2.3 next-hop-self |
To watch packets, run tcpdump -lnni eth0 tcp port 179 in a second shell on the router (docker compose -f lab-compose.yaml exec r1 sh). To follow the FSM, debug bgp neighbor-events in vtysh together with log file /var/log/frr/frr.log debugging, then read the log.
On macOS and Windows, Docker Desktop's kernel has no TCP-MD5 support, so the password scenario won't fail the way it would on a real router. Most Linux distribution kernels do include TCP-MD5, so on a Linux host it should behave like a real router (we haven't captured that here).
# BGP debugging lab: r1 (AS 64500) -- r2 (AS 64501) -- r3 (AS 64501).
# Run from this directory: docker compose -f lab-compose.yaml up -d --build
# Then: docker compose -f lab-compose.yaml exec r1 vtysh
name: bgp-debug-lab
x-router: &router
build: .
image: ttr-frr-debug:10.2.1
privileged: true # iptables and tcpdump inside the routers
sysctls: { net.ipv4.ip_forward: 1 }
services:
r1:
<<: *router
hostname: r1
volumes: ["./daemons:/etc/frr/daemons:ro", "./r1.conf:/etc/frr/frr.conf:ro"]
networks: { r1r2: { interface_name: eth0 } }
r2:
<<: *router
hostname: r2
volumes: ["./daemons:/etc/frr/daemons:ro", "./r2.conf:/etc/frr/frr.conf:ro"]
networks: { r1r2: { interface_name: eth0 }, r2r3: { interface_name: eth1 } }
r3:
<<: *router
hostname: r3
volumes: ["./daemons:/etc/frr/daemons:ro", "./r3.conf:/etc/frr/frr.conf:ro"]
networks: { r2r3: { interface_name: eth0 } }
# Layer-2-only links: Docker assigns no IPv4, so FRR's addresses are the only ones.
networks:
r1r2:
enable_ipv4: false
enable_ipv6: true
ipam: { config: [{ subnet: "fd00:12::/64" }] }
r2r3:
enable_ipv4: false
enable_ipv6: true
ipam: { config: [{ subnet: "fd00:23::/64" }] }- Lines 5–9: One definition shared by all three routers: the FRR image with iptables and tcpdump, privileged so those tools work, and IP forwarding so r2 routes between r1 and r3.
# FRR 10.2.1 plus the tools the lab uses to break things and watch them break.
FROM quay.io/frrouting/frr:10.2.1
RUN apk add --no-cache iptables tcpdump \
&& mkdir -p /var/log/frr && chown frr:frr /var/log/frr \
&& echo "service integrated-vtysh-config" > /etc/frr/vtysh.confbgpd=yes
ospfd=no
ospf6d=no
ripd=no
ripngd=no
isisd=no
pimd=no
ldpd=no
nhrpd=no
eigrpd=no
babeld=no
sharpd=no
pbrd=no
bfdd=no
fabricd=no
vrrpd=no
pathd=no
staticd=yes
vtysh_enable=yes
zebra_options=" -A 127.0.0.1 -s 90000000"
bgpd_options=" -A 127.0.0.1"
staticd_options="-A 127.0.0.1"Where next
- BGP FundamentalsPrerequisite · Intermediate · 75 min
How the Border Gateway Protocol glues the internet together: autonomous systems, sessions, path attributes, best-path selection, iBGP scaling and a hands-on eBGP lab with real FRR configuration.
- Packets That Never Come BackAlso covers troubleshooting, tcpdump · Intermediate · 75 min
Advanced packet capture with tcpdump, and how to find where packets die on a VyOS network: missing NAT and return routes, asymmetric routing through a stateful firewall, and MTU black holes, all diagnosed on a real VyOS router.
- EVPN and VXLAN: layer 2 over a routed fabricAlso covers BGP · Advanced · 45 min
Stretch a layer-2 segment across a routed leaf-spine fabric: VXLAN for the data plane, BGP EVPN for the control plane. Built with FRR: the underlay, VNIs, type-2 and type-3 routes, route targets, and the VXLAN packets on the wire.
- Linux Network ConnectivityAlso covers troubleshooting · Beginner · 80 min
Find out why a Linux machine can't reach something, layer by layer: interfaces and addresses, routes and ip route get, ARP and the neighbour table, ping and traceroute, DNS, and listening sockets — with real output from a three-host lab.