TTR Networks

Beginner · Linux Network Connectivity · section 11 of 11

Build the lab, and make it permanent

✓ Lab-tested 17 October 2026 on Debian 12

Build the lab with Docker: three containers joined by two layer-2-only networks, so every address in the lesson is set by the commands below and nothing is assigned automatically.

Remember that everything set with ip and sysctl -w disappears on reboot. To make the client's configuration permanent on Ubuntu, describe it in netplan. Netplan doesn't configure the network itself: it generates configuration for systemd-networkd (or NetworkManager), which you can inspect after netplan generate.

Use netplan try rather than netplan apply on a remote machine: it rolls back automatically unless you confirm, so a typo can't lock you out.

Lab compose file
# docker compose up -d --build, then: docker compose exec client bash
# Run the commands from lab-setup.sh in each container.
name: linux-lab
x-host: &host
  image: ttr-linux-lab
  privileged: true
services:
  client:
    <<: *host
    build:
      dockerfile_inline: |
        FROM debian:bookworm-slim
        RUN apt-get update && apt-get install -y --no-install-recommends \
          iproute2 iputils-ping iputils-tracepath traceroute dnsutils curl \
          tcpdump netcat-openbsd nftables dnsmasq python3 procps ethtool
        CMD ["sleep", "infinity"]
    hostname: client
    networks: { office: { interface_name: eth0 } }
  router:
    <<: *host
    hostname: router
    networks: { office: { interface_name: eth0 }, dmz: { interface_name: eth1 } }
  server:
    <<: *host
    hostname: server
    networks: { dmz: { interface_name: eth0 } }
# Layer-2-only networks: Docker assigns no IPv4, so every address comes from lab-setup.sh.
networks:
  office: { enable_ipv4: false, enable_ipv6: true, ipam: { config: [{ subnet: "fd00:a::/64" }] } }
  dmz: { enable_ipv4: false, enable_ipv6: true, ipam: { config: [{ subnet: "fd00:b::/64" }] } }
  • Line 6: Privileged, because the lab changes kernel settings with sysctl -w (forwarding), and Docker only makes /proc/sys writable for privileged containers. Fine for a throwaway lab; never do this for real services.
  • Lines 10–16: The toolbox image: iproute2, ping, tracepath, traceroute, dig, curl, tcpdump, nc, nftables, dnsmasq and python3.
  • Line 18: interface_name pins names, so eth0/eth1 always match the commands in the lesson.
  • Lines 27–30: Docker insists on one address family per network, so it gets IPv6 only; IPv4 is left entirely to you. In the lesson's lab, IPv6 was also disabled inside the containers to keep the output simple.
Lab setup commands
# router: two interfaces, forwarding on, and a tiny DNS server for lab.example
ip addr add 192.0.2.1/24 dev eth0
ip addr add 198.51.100.1/24 dev eth1
sysctl -w net.ipv4.ip_forward=1
dnsmasq --no-resolv --no-hosts --bind-interfaces --listen-address=192.0.2.1 \
  --local=/lab.example/ --address=/server.lab.example/198.51.100.20

# server: address, default route, a web server on :80, and port 8443 silently dropped
ip addr add 198.51.100.20/24 dev eth0
ip route add default via 198.51.100.1
python3 -m http.server 80 &
nft add table inet filter
nft add chain inet filter input '{ type filter hook input priority 0; }'
nft add rule inet filter input tcp dport 8443 drop

# client: address, default route and resolver
ip addr add 192.0.2.10/24 dev eth0
ip route add default via 192.0.2.1
echo "nameserver 192.0.2.1" > /etc/resolv.conf
  • Lines 2–4: A router is just a host with two connected subnets and forwarding enabled.
  • Lines 5–6: dnsmasq answers for lab.example and returns NXDOMAIN for other names in it.
  • Line 10: The server's route back. Delete it to reproduce fault 3.
  • Lines 12–14: An nftables rule that drops port 8443, so connections time out rather than being refused.
  • Lines 17–19: The client: address, default gateway and resolver. These three lines are what DHCP normally does for you.
Making the client permanent with netplan
# /etc/netplan/01-eth0.yaml — the client's settings, made permanent (Ubuntu).
# Apply with: netplan try   (rolls back automatically if you lose access)
network:
  version: 2
  ethernets:
    eth0:
      addresses:
        - 192.0.2.10/24
      routes:
        - to: default
          via: 192.0.2.1
      nameservers:
        addresses:
          - 192.0.2.1
  • Lines 7–8: Static address with prefix, the equivalent of ip addr add.
  • Lines 9–11: The default route. (gateway4: is deprecated; use routes with to: default.)
  • Lines 12–14: DNS servers, which end up in systemd-resolved rather than written directly to /etc/resolv.conf.
What netplan generates (real output, Ubuntu 24.04)
root@ubuntu:~# netplan generate
root@ubuntu:~# cat /run/systemd/network/10-netplan-eth0.network
[Match]
Name=eth0

[Network]
LinkLocalAddressing=ipv6
Address=192.0.2.10/24
DNS=192.0.2.1

[Route]
Destination=0.0.0.0/0
Gateway=192.0.2.1
  • Line 1: Renders the YAML into backend configuration. Silent on success.
  • Lines 3–13: A systemd-networkd unit. This, not the YAML, is what actually configures the interface at boot.

Where next

  • Packets That Never Come BackBuilds on this lesson · 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.

  • Setting up VyOS with NATBuilds on this lesson · 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.

  • Office gateway: DHCP, DNS and VRRP on VyOSAlso covers DNS · 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.

  • Debugging BGP: every session state, reproducedAlso covers troubleshooting · Intermediate · 60 min

    Break a working BGP lab a dozen ways and learn to read each failure: Idle, Connect, Active and OpenSent, max-prefix, firewalls, wrong AS, TTL, hold timers, missing policy and unreachable next hops, all with real FRR output and packet captures.