124 lines
3.1 KiB
Bash
Executable file
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
|