OpenWRT en Proxmox VE

Contexto HAaaS: Esta VM actúa como router interno con roles de DHCP y DNS para la red de clientes (vmbr1). La WAN conecta con la red interna de Proxmox (vmbr0) que tiene salida a GCP/internet.

Arquitectura de red

Internet (GCP)
      │
   ens4 (10.132.0.7) — IP real GCP
      │
 Proxmox Host
      │
   vmbr0 (192.168.100.1/24) ←── WAN OpenWRT (eth0 → 192.168.100.2)
      │
   OpenWRT VM
      │
   vmbr1 (eth1 → br-lan → 10.10.10.2/24) ←── LAN clientes
      │
   VMs de clientes (Home Assistant, etc.)

Requisitos previos

1. Descargar la imagen de OpenWRT

Conectarse al host Proxmox por SSH y ejecutar:

cd /tmp

wget https://downloads.openwrt.org/releases/23.05.3/targets/x86/64/openwrt-23.05.3-x86-64-generic-ext4-combined.img.gz

gunzip openwrt-23.05.3-x86-64-generic-ext4-combined.img.gz
Nota

Se usa la imagen x86-64 ext4 combined porque es la más adecuada para virtualización. No requiere instalador.

2. Crear la VM

qm create 200 \
  --name openwrt \
  --memory 512 \
  --cores 1 \
  --net0 virtio,bridge=vmbr0 \
  --net1 virtio,bridge=vmbr1 \
  --ostype l26 \
  --serial0 socket \
  --vga serial0
Parámetro Valor Descripción
VMID 200 Cambiar si está en uso
Memory 512 MB Suficiente para DHCP/DNS
net0 vmbr0 WAN → red interna Proxmox
net1 vmbr1 LAN → red de clientes

3. Importar el disco

qm importdisk 200 /tmp/openwrt-23.05.3-x86-64-generic-ext4-combined.img local-lvm
Warning

El disco se importa como ide0, no como scsi0. Hay que ajustar el boot en consecuencia.

Verificar que el disco quedó bien:

qm config 200

La salida debe mostrar ide0: ... con el disco. Ajustar el boot:

qm set 200 --boot order=ide0

4. Arrancar la VM

qm start 200

Acceder a la consola desde la web UI de Proxmox (Console → NoVNC), no por terminal serial (el serial puede no responder hasta tener la red configurada).

5. Configurar interfaces de red

Por defecto OpenWRT asigna eth0 a LAN y eth1 a WAN. En Proxmox, net0=vmbr0 (WAN) se mapea a eth0 y net1=vmbr1 (LAN) a eth1, por lo que hay que reconfigurar.

Ejecutar en la consola de OpenWRT:

# Mover eth1 al bridge de LAN (en vez de eth0)
uci set network.@device[0].ports='eth1'

# WAN en eth0 (vmbr0)
uci set network.wan.proto='static'
uci set network.wan.ipaddr='192.168.100.2'
uci set network.wan.netmask='255.255.255.0'
uci set network.wan.gateway='192.168.100.1'
uci set network.wan.dns='8.8.8.8'

# LAN en eth1/br-lan (vmbr1)
uci set network.wan.device='eth0'
uci set network.lan.ipaddr='10.10.10.2'
uci set network.lan.netmask='255.255.255.0'

uci commit network
/etc/init.d/network restart
Nota

GCP no responde a DHCP broadcast desde VMs internas, por eso la WAN se configura como IP estática dentro de vmbr0.

6. Verificar conectividad

# Verificar IPs asignadas
ip addr show eth0      # debe mostrar 192.168.100.2
ip addr show br-lan    # debe mostrar 10.10.10.2

# Ping al host Proxmox (gateway WAN)
ping -c 3 192.168.100.1

# Ping a internet
ping -c 3 1.1.1.1

7. Verificar DHCP y DNS

/etc/init.d/dnsmasq status

El servicio dnsmasq gestiona tanto DHCP como DNS en OpenWRT por defecto. La configuración base está en /etc/config/dhcp.

Para ver los leases activos:

cat /tmp/dhcp.leases

8. Configurar acceso SSH sin contraseña desde Proxmox

El hookscript necesita SSH sin contraseña hacia OpenWRT. Los pasos son:

8.1 Generar clave SSH (si no existe)

Desde el host Proxmox, generar una clave RSA de 4096 bits:

ssh-keygen -t rsa -b 4096 -N "" -f /root/.ssh/id_rsa

Esto crea /root/.ssh/id_rsa (clave privada) y /root/.ssh/id_rsa.pub (clave pública).

8.2 Copiar clave pública a OpenWRT

ssh-copy-id root@192.168.100.2

Esto añade la clave pública a /root/.ssh/authorized_keys en OpenWRT.

8.3 Verificar conexión

ssh root@192.168.100.2 "echo ok"

Debe responder ok sin pedir contraseña.

Nota

Si aparece el error WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED (por ejemplo tras recrear la VM OpenWRT), ejecutar primero:

ssh-keygen -f "/root/.ssh/known_hosts" -R "192.168.100.2"

9. Desactivar el firewall

OpenWRT tiene un firewall activo por defecto que bloquea SSH desde la WAN. Para el uso interno de HAaaS se desactiva completamente:

/etc/init.d/firewall stop
/etc/init.d/firewall disable

10. Fijar ruta de retorno hacia la red LAN

Por defecto OpenWRT usa eth0 (WAN) como interfaz de origen al contactar 10.10.10.0/24, lo que hace que las VMs no puedan responder. Hay que forzar que el tráfico LAN salga siempre por br-lan:

uci add network route
uci set network.@route[-1].interface='lan'
uci set network.@route[-1].target='10.10.10.0'
uci set network.@route[-1].netmask='255.255.255.0'
uci set network.@route[-1].gateway='10.10.10.2'
uci commit network
/etc/init.d/network restart
Nota

lo que hace que las VMs no puedan responder. Hay que forzar que el tráfico LAN salga siempre por br-lan:

Verificar:

ping -c 3 10.10.10.100

Estado final esperado

Interfaz IP Red Proxmox Rol
eth0 192.168.100.2/24 vmbr0 WAN
br-lan (eth1) 10.10.10.2/24 vmbr1 LAN / DHCP / DNS

Hookscript de reservas DHCP

El hookscript se ejecuta automáticamente en el host Proxmox cada vez que una VM arranca o se detiene. Se comunica con OpenWRT vía SSH para registrar o eliminar la reserva DHCP de esa VM.

Flujo:

Instalar el hookscript

mkdir -p /var/lib/vz/snippets

cat > /var/lib/vz/snippets/hookscript.sh << 'EOF'
#!/usr/bin/env bash
set -euo pipefail

# ─────────────────────────────────────────────────────────────────────────────
# hookscript.sh — Gestiona reservas DHCP en OpenWRT por MAC
#
# Proxmox ejecuta este script con dos argumentos:
#   $1 = VMID
#   $2 = PHASE (pre-start | post-start | pre-stop | post-stop)
#
# Requisitos:
#   - Acceso SSH sin contraseña desde Proxmox a OpenWRT
#   - Para configurarlo: ssh-copy-id root@192.168.100.2
#
# Regla de IPs: cada VM recibe 10.10.10. — el VMID debe ser 1-254.
# ─────────────────────────────────────────────────────────────────────────────

VMID="$1"
PHASE="$2"
OPENWRT_IP="192.168.100.2"   # IP WAN de la VM OpenWRT en vmbr0

log()  { echo "[hookscript:${VMID}] $*"; }
err()  { echo "[hookscript:${VMID}] ERROR: $*" >&2; exit 1; }

# Obtener la MAC de net0 de esta VM
get_mac() {
    grep "^net0:" "/etc/pve/qemu-server/${VMID}.conf" 2>/dev/null \
        | grep -oP '([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}' \
        | head -1
}

# Verificar que OpenWRT es accesible por SSH antes de operar
check_openwrt() {
    ssh -o ConnectTimeout=5 -o BatchMode=yes \
        "root@${OPENWRT_IP}" "exit" 2>/dev/null \
        || err "No se puede conectar a OpenWRT (${OPENWRT_IP}). Comprueba SSH y que la VM OpenWRT está arriba."
}

# Ejecutar un comando en OpenWRT via SSH
owrt() {
    ssh -o ConnectTimeout=5 -o BatchMode=yes \
        "root@${OPENWRT_IP}" "$*" 2>/dev/null
}

# Eliminar TODAS las reservas DHCP existentes para una MAC dada.
# Se borran en orden inverso para no romper los índices del array uci.
remove_existing_reservation() {
    local mac="$1"
    local indices

    indices=$(owrt "/sbin/uci show dhcp" \
        | grep -i "\.mac='${mac}'" \
        | grep -oP '@host\[\K[0-9]+' \
        | sort -rn || true)

    for idx in $indices; do
        log "Eliminando reserva existente dhcp.@host[${idx}] (${mac})"
        owrt "/sbin/uci delete dhcp.@host[${idx}]"
    done

    if [[ -n "$indices" ]]; then
        owrt "/sbin/uci commit dhcp"
    fi
}

reload_dnsmasq() {
    owrt "/etc/init.d/dnsmasq restart"
    log "dnsmasq reiniciado."
}

# Validar que el VMID puede usarse como último octeto de IP
validate_ip_from_vmid() {
    local vmid="$1"
    if [[ "$vmid" -lt 1 || "$vmid" -gt 254 ]]; then
        err "VMID $vmid fuera de rango para IP 10.10.10.x (debe ser entre 1 y 254)"
    fi
}

# ── Lógica por fase ───────────────────────────────────────────────────────────
case "$PHASE" in
    pre-start)
        validate_ip_from_vmid "$VMID"
        MAC=$(get_mac)
        IP="10.10.10.${VMID}"

        [[ -z "$MAC" ]] && err "No se encontró MAC en net0 para VMID $VMID"

        log "Fase pre-start: reservando ${MAC} → ${IP}"
        check_openwrt

        # Limpiar duplicados antes de añadir (por si la VM se reinició antes)
        remove_existing_reservation "$MAC"

        # Registrar reserva nueva
        owrt "/sbin/uci add dhcp host"
        owrt "/sbin/uci set dhcp.@host[-1].name='haas-vm-${VMID}'"
        owrt "/sbin/uci set dhcp.@host[-1].mac='${MAC}'"
        owrt "/sbin/uci set dhcp.@host[-1].ip='${IP}'"
        owrt "/sbin/uci commit dhcp"

        log "Reserva DHCP registrada: ${MAC} → ${IP}"
        reload_dnsmasq
        ;;

    post-stop)
        MAC=$(get_mac)
        [[ -z "$MAC" ]] && { log "No se encontró MAC, nada que limpiar."; exit 0; }

        log "Fase post-stop: eliminando reserva para ${MAC}"
        check_openwrt
        remove_existing_reservation "$MAC"
        reload_dnsmasq
        ;;

    # Las fases post-start y pre-stop no requieren acción
    *)
        exit 0
        ;;
esac

exit 0
EOF

chmod +x /var/lib/vz/snippets/hookscript.sh

11. Habilitar IP Forwarding y NAT

Las VMs en la red interna (vmbr1) necesitan acceso a internet a través de OpenWRT. Para esto hay que habilitar el reenvío de IP y configurar NAT (Masquerade).

11.1 Habilitar IP Forwarding

# Activar inmediatamente
sysctl -w net.ipv4.ip_forward=1

# Hacer que el cambio sea permanente
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf

11.2 Configurar NAT (Masquerade)

# Activar enmascaramiento en la zona WAN
uci set firewall.@zone[1].masq='1'
uci set firewall.wan.masq='1'

# Aplicar cambios y reiniciar el firewall
uci commit firewall
/etc/init.d/firewall restart

Verificación

Para confirmar que la configuración es correcta:

# Verificar regla de NAT
nft list ruleset | grep masquerade

# Desde una VM interna, verificar conectividad
ping -c 3 8.8.8.8
Nota

Esta configuración Permite que todas las máquinas de la red interna salgan a internet. Si en el futuro se requiere restringir el acceso a ciertos puertos, se deberán añadir reglas adicionales en /etc/config/firewall.

Notas y troubleshooting

Problema Causa Solución
iPXE al arrancar boot apuntando a net0 qm set 200 --boot order=ide0
Terminal serial no responde OpenWRT no activa serial por defecto Usar consola NoVNC desde web UI
eth0 sin IP tras DHCP GCP no responde a broadcast Configurar WAN como IP estática
Sin ping a internet Falta NAT en host Proxmox Verificar iptables MASQUERADE en ens4

Ver también: Configuración de Proxmox, Nginx Proxy Manager