Add per-VM NAT and DNS config
This commit is contained in:
parent
60b1865ece
commit
68cf5f2cbb
3 changed files with 165 additions and 4 deletions
16
README.md
16
README.md
|
|
@ -25,12 +25,28 @@ Minimal Firecracker launcher.
|
||||||
- `--vcpu`: defaults to 2, max 16.
|
- `--vcpu`: defaults to 2, max 16.
|
||||||
- `--ram`: MiB, defaults to 1024, max 32768.
|
- `--ram`: MiB, defaults to 1024, max 32768.
|
||||||
- `--disk-size`: M/G suffixes supported; must be >= base `rootfs.ext4` size. Requires `resize2fs`.
|
- `--disk-size`: M/G suffixes supported; must be >= base `rootfs.ext4` size. Requires `resize2fs`.
|
||||||
|
- `DNS_SERVERS`: optional env var for resolv.conf (default: `1.1.1.1`). Requires `debugfs`.
|
||||||
|
|
||||||
## SSH
|
## SSH
|
||||||
```
|
```
|
||||||
ssh -i "./id_ed25519" root@<guest_ip>
|
ssh -i "./id_ed25519" root@<guest_ip>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Internet Access
|
||||||
|
VMs do not get internet access by default. You must enable forwarding and NAT:
|
||||||
|
```
|
||||||
|
./nat.sh up <id-or-name-prefix>
|
||||||
|
```
|
||||||
|
This enables `net.ipv4.ip_forward=1` and installs per-VM NAT rules for the VM's
|
||||||
|
guest IP and TAP device. To remove rules:
|
||||||
|
```
|
||||||
|
./nat.sh down <id-or-name-prefix>
|
||||||
|
```
|
||||||
|
Check status with:
|
||||||
|
```
|
||||||
|
./nat.sh status <id-or-name-prefix>
|
||||||
|
```
|
||||||
|
|
||||||
## Shutdown
|
## Shutdown
|
||||||
```
|
```
|
||||||
reboot
|
reboot
|
||||||
|
|
|
||||||
130
nat.sh
Executable file
130
nat.sh
Executable file
|
|
@ -0,0 +1,130 @@
|
||||||
|
#!/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
|
||||||
|
}
|
||||||
|
|
||||||
|
get_prop() {
|
||||||
|
local info="$1"
|
||||||
|
local key="$2"
|
||||||
|
awk -F= -v k="$key" '$1==k {print $2}' "$info"
|
||||||
|
}
|
||||||
|
|
||||||
|
find_vm_info() {
|
||||||
|
local query="$1"
|
||||||
|
local info match_count=0 match=""
|
||||||
|
|
||||||
|
for info in state/vms/*/info; do
|
||||||
|
[[ -f "$info" ]] || continue
|
||||||
|
local id name
|
||||||
|
id="$(get_prop "$info" "id")"
|
||||||
|
name="$(get_prop "$info" "name")"
|
||||||
|
if [[ "$id" == "$query"* || "$name" == "$query"* ]]; then
|
||||||
|
match="$info"
|
||||||
|
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
|
||||||
|
|
||||||
|
INFO_FILE="$(find_vm_info "$QUERY")"
|
||||||
|
GUEST_IP="$(get_prop "$INFO_FILE" "guest_ip")"
|
||||||
|
TAP="$(get_prop "$INFO_FILE" "tap")"
|
||||||
|
|
||||||
|
if [[ -z "$GUEST_IP" || -z "$TAP" ]]; then
|
||||||
|
log "missing guest_ip or tap in $INFO_FILE"
|
||||||
|
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
|
||||||
23
run.sh
23
run.sh
|
|
@ -42,6 +42,7 @@ MAX_VCPU=16
|
||||||
MIN_RAM=256
|
MIN_RAM=256
|
||||||
MAX_RAM=32768
|
MAX_RAM=32768
|
||||||
MAX_DISK_BYTES=$((128 * 1024 * 1024 * 1024))
|
MAX_DISK_BYTES=$((128 * 1024 * 1024 * 1024))
|
||||||
|
DNS_SERVERS="${DNS_SERVERS:-1.1.1.1}"
|
||||||
|
|
||||||
VCPU_COUNT="$DEFAULT_VCPU"
|
VCPU_COUNT="$DEFAULT_VCPU"
|
||||||
RAM_MIB="$DEFAULT_RAM"
|
RAM_MIB="$DEFAULT_RAM"
|
||||||
|
|
@ -192,7 +193,7 @@ sudo -v
|
||||||
VM_STARTED=0
|
VM_STARTED=0
|
||||||
CLEANUP_ON_EXIT=0
|
CLEANUP_ON_EXIT=0
|
||||||
KEEP_VM_DIR_ON_FAIL=1
|
KEEP_VM_DIR_ON_FAIL=1
|
||||||
DISK_PATH="$ROOTFS"
|
DISK_PATH="$VM_DIR/rootfs.ext4"
|
||||||
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
local exit_code=$?
|
local exit_code=$?
|
||||||
|
|
@ -241,13 +242,13 @@ else
|
||||||
log "setcap not available; firecracker may need root to open TAP"
|
log "setcap not available; firecracker may need root to open TAP"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
cp --reflink=auto "$ROOTFS" "$DISK_PATH"
|
||||||
|
|
||||||
if [[ -n "$DISK_BYTES" ]]; then
|
if [[ -n "$DISK_BYTES" ]]; then
|
||||||
if ! command -v resize2fs >/dev/null 2>&1; then
|
if ! command -v resize2fs >/dev/null 2>&1; then
|
||||||
log "resize2fs required for --disk-size"
|
log "resize2fs required for --disk-size"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
DISK_PATH="$VM_DIR/rootfs.ext4"
|
|
||||||
cp --reflink=auto "$ROOTFS" "$DISK_PATH"
|
|
||||||
BASE_BYTES="$(stat -c%s "$ROOTFS")"
|
BASE_BYTES="$(stat -c%s "$ROOTFS")"
|
||||||
if (( DISK_BYTES < BASE_BYTES )); then
|
if (( DISK_BYTES < BASE_BYTES )); then
|
||||||
log "disk-size must be >= base image size"
|
log "disk-size must be >= base image size"
|
||||||
|
|
@ -260,6 +261,20 @@ if [[ -n "$DISK_BYTES" ]]; then
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if ! command -v debugfs >/dev/null 2>&1; then
|
||||||
|
log "debugfs required to set resolv.conf"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
RESOLV_TMP="$VM_DIR/resolv.conf"
|
||||||
|
printf '' >"$RESOLV_TMP"
|
||||||
|
for ns in ${DNS_SERVERS//,/ }; do
|
||||||
|
printf 'nameserver %s\n' "$ns" >>"$RESOLV_TMP"
|
||||||
|
done
|
||||||
|
debugfs -w -R "write $RESOLV_TMP /etc/resolv.conf" "$DISK_PATH" >/dev/null 2>&1 || {
|
||||||
|
log "failed to write /etc/resolv.conf into rootfs"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
# Host bridge
|
# Host bridge
|
||||||
if ! ip link show "$BR_DEV" >/dev/null 2>&1; then
|
if ! ip link show "$BR_DEV" >/dev/null 2>&1; then
|
||||||
log "creating host bridge $BR_DEV ($BR_IP/$CIDR)"
|
log "creating host bridge $BR_DEV ($BR_IP/$CIDR)"
|
||||||
|
|
@ -325,7 +340,7 @@ log "configuring machine"
|
||||||
|
|
||||||
# Boot source
|
# Boot source
|
||||||
log "configuring boot source"
|
log "configuring boot source"
|
||||||
KCMD="console=ttyS0 reboot=k panic=1 pci=off root=/dev/vda rw ip=${GUEST_IP}::${BR_IP}:255.255.255.0::eth0:off"
|
KCMD="console=ttyS0 reboot=k panic=1 pci=off root=/dev/vda rw ip=${GUEST_IP}::${BR_IP}:255.255.255.0::eth0:off hostname=${VM_NAME}"
|
||||||
|
|
||||||
"${CURL_CMD[@]}" --unix-socket "$API_SOCK" -X PUT http://localhost/boot-source \
|
"${CURL_CMD[@]}" --unix-socket "$API_SOCK" -X PUT http://localhost/boot-source \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue