banger/nat.sh

124 lines
3.1 KiB
Bash
Executable file

#!/usr/bin/env bash
set -euo pipefail
log() {
printf '[nat] %s\n' "$*"
}
usage() {
cat <<'EOF'
Usage: ./nat.sh <up|down|status> <id-or-name-prefix>
Manage per-VM NAT rules for internet access.
EOF
}
find_vm_json() {
local query="$1"
local vm_json match_count=0 match=""
for vm_json in state/vms/*/vm.json; do
[[ -f "$vm_json" ]] || continue
local id name
id="$(jq -r '.meta.id // empty' "$vm_json")"
name="$(jq -r '.meta.name // empty' "$vm_json")"
if [[ "$id" == "$query"* || "$name" == "$query"* ]]; then
match="$vm_json"
match_count=$((match_count + 1))
fi
done
if (( match_count == 0 )); then
log "no VM found for prefix: $query"
exit 1
fi
if (( match_count > 1 )); then
log "multiple VMs found for prefix: $query"
exit 1
fi
printf '%s' "$match"
}
default_uplink() {
ip route show default 2>/dev/null | awk '/default/ {print $5; exit}'
}
ensure_iptables() {
if ! command -v iptables >/dev/null 2>&1; then
log "iptables not found"
exit 1
fi
}
ACTION="${1:-}"
QUERY="${2:-}"
if [[ -z "$ACTION" || -z "$QUERY" ]]; then
usage
exit 1
fi
VM_JSON="$(find_vm_json "$QUERY")"
GUEST_IP="$(jq -r '.meta.guest_ip // empty' "$VM_JSON")"
TAP="$(jq -r '.meta.tap // empty' "$VM_JSON")"
if [[ -z "$GUEST_IP" || -z "$TAP" ]]; then
log "missing guest_ip or tap in $VM_JSON"
exit 1
fi
UPLINK="$(default_uplink)"
if [[ -z "$UPLINK" ]]; then
log "failed to detect uplink interface"
exit 1
fi
ensure_iptables
nat_rule=(-t nat -s "${GUEST_IP}/32" -o "$UPLINK" -j MASQUERADE)
fwd_out_rule=(-i "$TAP" -o "$UPLINK" -j ACCEPT)
fwd_in_rule=(-i "$UPLINK" -o "$TAP" -m state --state "RELATED,ESTABLISHED" -j ACCEPT)
case "$ACTION" in
up)
sudo sysctl -w net.ipv4.ip_forward=1 >/dev/null
sudo iptables -t nat -C POSTROUTING "${nat_rule[@]}" 2>/dev/null || \
sudo iptables -t nat -A POSTROUTING "${nat_rule[@]}"
sudo iptables -C FORWARD "${fwd_out_rule[@]}" 2>/dev/null || \
sudo iptables -A FORWARD "${fwd_out_rule[@]}"
sudo iptables -C FORWARD "${fwd_in_rule[@]}" 2>/dev/null || \
sudo iptables -A FORWARD "${fwd_in_rule[@]}"
log "NAT enabled for $GUEST_IP via $UPLINK"
;;
down)
sudo iptables -t nat -C POSTROUTING "${nat_rule[@]}" 2>/dev/null && \
sudo iptables -t nat -D POSTROUTING "${nat_rule[@]}" || true
sudo iptables -C FORWARD "${fwd_out_rule[@]}" 2>/dev/null && \
sudo iptables -D FORWARD "${fwd_out_rule[@]}" || true
sudo iptables -C FORWARD "${fwd_in_rule[@]}" 2>/dev/null && \
sudo iptables -D FORWARD "${fwd_in_rule[@]}" || true
log "NAT disabled for $GUEST_IP"
;;
status)
sysctl net.ipv4.ip_forward | sed 's/^/[nat] /'
if sudo iptables -t nat -C POSTROUTING "${nat_rule[@]}" 2>/dev/null; then
log "nat: installed"
else
log "nat: missing"
fi
if sudo iptables -C FORWARD "${fwd_out_rule[@]}" 2>/dev/null; then
log "forward out: installed"
else
log "forward out: missing"
fi
if sudo iptables -C FORWARD "${fwd_in_rule[@]}" 2>/dev/null; then
log "forward in: installed"
else
log "forward in: missing"
fi
;;
*)
usage
exit 1
;;
esac