#!/usr/bin/env bash # Linux-only isolated lab: no links to a physical interface or host routing edits. set -euo pipefail [[ $(uname -s) == Linux ]] || { echo 'Use an isolated Linux VM.' >&2; exit 1; } [[ $EUID -eq 0 ]] || { echo 'Run as root inside your Linux VM.' >&2; exit 1; } for executable in ip ping; do command -v "$executable" >/dev/null || { echo "Missing: $executable" >&2; exit 1; } done prefix="course-l15-$$" h="${prefix}-h"; sw="${prefix}-sw"; g1="${prefix}-g1"; g2="${prefix}-g2" created=() cleanup() { for ns in "${created[@]}"; do ip netns del "$ns" || true; done } trap cleanup EXIT trap 'exit 130' INT trap 'exit 143' TERM for ns in "$h" "$sw" "$g1" "$g2"; do if ip netns list | awk '{print $1}' | grep -Fxq "$ns"; then echo "Existing namespace; refusing to alter: $ns" >&2; exit 1 fi ip netns add "$ns"; created+=("$ns") ip -n "$ns" link set lo up done printf 'Isolated namespaces: %s %s %s %s\n' "$h" "$sw" "$g1" "$g2" ip -Version uname -r ip -n "$sw" link add br0 type bridge ip -n "$sw" link set br0 up for pair in "hport:$h" "g1port:$g1" "g2port:$g2"; do port=${pair%%:*}; ns=${pair#*:} ip -n "$sw" link add "$port" type veth peer name eth0 netns "$ns" ip -n "$sw" link set "$port" master br0 ip -n "$sw" link set "$port" up ip -n "$ns" link set eth0 up done ip -n "$h" addr add 192.0.2.10/24 dev eth0 ip -n "$h" addr add 198.51.100.10/24 dev eth0 ip -n "$g1" addr add 192.0.2.1/24 dev eth0 ip -n "$g2" addr add 198.51.100.1/24 dev eth0 ip -n "$g1" addr add 203.0.113.80/32 dev lo ip -n "$g2" addr add 203.0.113.80/32 dev lo ip -n "$g1" route add 198.51.100.0/24 via 192.0.2.10 dev eth0 ip -n "$h" route add default via 192.0.2.1 dev eth0 src 192.0.2.10 printf '\nBefore policy: bound source2 still uses G1\n' ip -n "$h" addr show dev eth0 ip -n "$h" rule show ip -n "$h" route show table main ip -n "$h" route show table local ip -n "$h" route get 203.0.113.80 from 198.51.100.10 ip netns exec "$h" ping -c 2 -W 2 -I 198.51.100.10 203.0.113.80 # Create a valid G2 default using its connected route, then remove that prefix. # The next hop really is on this isolated link. The existing default is retained. ip -n "$h" route add table 200 198.51.100.0/24 dev eth0 src 198.51.100.10 ip -n "$h" route add table 200 default via 198.51.100.1 dev eth0 src 198.51.100.10 ip -n "$h" route del table 200 198.51.100.0/24 ip -n "$h" rule add priority 100 from 198.51.100.10/32 lookup 200 printf '\nBad LAN policy: table200 contains only default\n' ip -n "$h" route show table 200 ip -n "$h" route get 198.51.100.55 from 198.51.100.10 printf '\nCorrected table: direct LAN routes restored\n' ip -n "$h" route add table 200 192.0.2.0/24 dev eth0 src 192.0.2.10 ip -n "$h" route add table 200 198.51.100.0/24 dev eth0 src 198.51.100.10 ip -n "$h" route show table 200 ip -n "$h" route get 198.51.100.55 from 198.51.100.10 ip -n "$h" route get 203.0.113.80 from 198.51.100.10 ip -n "$h" route get 203.0.113.80 ip -n "$h" route get 192.0.2.10 from 198.51.100.10 ip netns exec "$h" ping -c 2 -W 2 -I 198.51.100.10 203.0.113.80 ip netns exec "$h" ping -c 2 -W 2 -I 198.51.100.10 198.51.100.1 ip -n "$h" rule add priority 50 to 203.0.113.81/32 lookup main printf '\nEndpoint route lookup only; no responder at .81\n' ip -n "$h" route get 203.0.113.81 from 198.51.100.10 ip -n "$h" rule show ip -n "$h" neigh show dev eth0 printf '\nDone. Namespaces are removed by the EXIT trap.\n'