Problema

En entornos de Proxmox que utilizan SDN para conectar contenedores LXC a una red externa, es frecuente encontrarse con que los contenedores pueden resolver nombres DNS y responder a pings, pero fallan al intentar abrir conexiones TCP a puertos 80 o 443. El síntoma típico es una serie de paquetes SYN enviados desde la IP del contenedor hacia la dirección pública, sin que el servidor remoto responda. En la captura de tráfico del host se observan los paquetes de salida, pero nunca aparecen respuestas ni retransmisiones de ACK. El problema se manifiesta de forma aislada: algunos contenedores (por ejemplo, un HAProxy) funcionan, mientras que otros quedan “ciegos” para HTTP/HTTPS.

Este patrón indica que el tráfico saliente no está siendo traducido correctamente por la SNAT (Source NAT) configurada en la capa de firewall o en las reglas de nftables. Cuando la traducción falla, el paquete sale con una dirección de origen que no es enrutable desde Internet, y el servidor remoto descarta la conexión.

Causa

Varias causas pueden producir este comportamiento en una configuración SDN de Proxmox:

  1. Regla de SNAT incompleta o mal posicionada
    En nftables la cadena postrouting debe aplicar masquerade a todo el tráfico que salga del rango interno (192.168.18.0/24). Si la regla está después de una regla return que coincide con la misma rango, el paquete nunca llega a la regla de masquerade.

  2. Reglas de firewall en el contenedor que bloquean puertos de salida
    El firewall interno del LXC (o la política DROP por defecto del host) puede permitir DNS y ICMP pero negar cualquier otro puerto TCP. En el ejemplo, se aceptan puertos 53 y 8006, pero no 80/443.

  3. Política de NAT en el host que sobrescribe la SNAT del datacenter
    Proxmox permite habilitar SNAT a nivel de datacenter y también a nivel de host. Si ambas están activas y la regla del host es más restrictiva, la traducción del datacenter nunca se ejecuta.

  4. Interfaz de salida equivocada
    En configuraciones SDN, la interfaz virtual (Vnet0) puede estar vinculada a una zona que no tiene ruta predeterminada hacia la interfaz física que lleva la IP pública. El tráfico se envía por una ruta que no tiene NAT.

  5. Regla return en la cadena postrouting que excluye la subred
    La regla ip daddr 192.168.18.0/24 return impide que los paquetes cuyo destino sea la subred interna se sometan a masquerade, pero también puede capturar paquetes con destino externo si la coincidencia es demasiado amplia.

  6. Conflicto entre iptables y nftables
    Si el host tiene reglas iptables activas que manipulan NAT, pueden interferir con nftables, provocando que el paquete sea marcado como “already NATed” y se descarte.

Solución

Una solución robusta se basa en tres pilares: (a) garantizar que la cadena postrouting aplique masquerade a todo el tráfico saliente del rango interno, (b) revisar y simplificar las políticas de firewall en los contenedores, y (c) validar que la ruta predeterminada del host apunte a la interfaz con la IP pública.

1. Normalizar la cadena postrouting

# Borrar reglas existentes que puedan interferir
nft flush table inet nat

# Crear tabla y cadena postrouting con política accept
nft add table inet nat
nft 'add chain inet nat postrouting { type nat hook postrouting priority srcnat \; policy accept }'

# Regla de masquerade para todo el rango interno
nft add rule inet nat postrouting ip saddr 192.168.18.0/24 oifname "vmbr0" masquerade

Puntos clave:

  • oifname "vmbr0" asegura que la NAT se aplique solo cuando el paquete sale por la interfaz que tiene la IP pública.
  • No se incluye ninguna regla return antes de masquerade; de lo contrario, el paquete se detendría.

2. Simplificar firewall en los contenedores

En la mayoría de los casos, basta con una política accept para tráfico saliente y reglas específicas para puertos que deben permanecer bloqueados. Un ejemplo mínimo dentro del contenedor:

# Dentro del LXC
nft flush table inet filter
nft add table inet filter
nft add chain inet filter output { type filter hook output priority 0 \; policy accept \; }
# Opcional: bloquear puertos no deseados
# nft add rule inet filter output tcp dport {23, 25} drop

Si se necesita mantener reglas de entrada, asegúrese de que no haya una regla output que niegue 80/443.

3. Verificar rutas y interfaces

# En el host
ip route show default
# Debe apuntar a la interfaz que tiene la IP pública, por ejemplo:
# default via 203.0.113.1 dev vmbr0 proto static

# Verificar que la zona SDN tenga la puerta de enlace correcta
pvesh get /nodes/<node>/sdn/zones/CTs
# La puerta de enlace debe ser 192.168.18.1

Si la ruta predeterminada apunta a vnet0 o a otra interfaz sin NAT, corríjala:

ip route replace default via 203.0.113.1 dev vmbr0

4. Desactivar reglas redundantes de SNAT a nivel de datacenter

En la UI de Proxmox, desactive la opción “SNAT” en el nivel de datacenter si ya está gestionada por nftables en el host. Mantener ambas activas genera una doble‑NAT que a veces bloquea la respuesta del servidor remoto.

5. Revisar coexistencia iptables/nftables

# Listar reglas iptables que manipulan NAT
iptables -t nat -L -nv
# Si aparecen reglas que hacen SNAT/MASQUERADE, elimínelas
iptables -t nat -F

Proxmox usa nftables por defecto; mantener iptables limpio evita colisiones.

Cuándo aplicar esta solución

  • Síntomas: DNS funciona, ICMP responde, pero curl http://... o curl https://... nunca recibe respuesta; los paquetes SYN aparecen en tcpdump pero no hay ACK.
  • Entorno: Proxmox con SDN, contenedores LXC en una zona que usa una subred privada (p.ej., 192.168.18.0/24) y una única IP pública en vmbr0.
  • No aplica: Cuando el tráfico saliente se enruta a través de un router externo que ya realiza NAT, o cuando se usa una solución de proxy transparente que gestiona la traducción.

Código

# 1. Resetear tabla NAT y crear regla de masquerade
nft flush table inet nat
nft add table inet nat
nft 'add chain inet nat postrouting { type nat hook postrouting priority srcnat \; policy accept }'
nft add rule inet nat postrouting ip saddr 192.168.18.0/24 oifname "vmbr0" masquerade

# 2. Simplificar firewall del contenedor (ejemplo dentro del LXC)
nft flush table inet filter
nft add table inet filter
nft add chain inet filter output { type filter hook output priority 0 \; policy accept \; }

# 3. Verificar y corregir ruta por defecto del host
ip route show default
ip route replace default via <GATEWAY_PUBLIC_IP> dev vmbr0

# 4. Eliminar reglas NAT en iptables (si existen)
iptables -t nat -F

Verificación

  1. Comprobar NAT

    nft list chain inet nat postrouting
    

    Debería mostrarse la regla masquerade con ip saddr 192.168.18.0/24.

  2. Probar conectividad
    Desde el contenedor:

    curl -I https://www.google.com
    

    Debería devolver encabezados HTTP sin tiempo de espera.

  3. Captura de tráfico
    En el host:

    tcpdump -i vmbr0 -n host 192.168.18.58 and port 443
    

    Verifique que aparecen paquetes SYN y SYN‑ACK, y que la dirección de origen es la IP pública del host, no la interna.

  4. Revisar logs de firewall

    journalctl -u nftables
    

    No deben aparecer denegaciones para puertos 80/443.

Notas adicionales

  • En configuraciones con varios rangos internos, repita la regla masquerade para cada subred o use ip saddr 10.0.0.0/8 según convenga.
  • Si necesita que algunos contenedores mantengan su IP original (por ejemplo, para listas blancas externas), añada excepciones antes de la regla masquerade usando ip saddr <IP> accept.
  • Cuando se habilita SNAT a nivel de datacenter, recuerde desactivar la regla masquerade en el host para evitar doble NAT.
  • En entornos con alta concurrencia, considere usar nft add rule inet nat postrouting oifname "vmbr0" ip saddr 192.168.18.0/24 counter masquerade para obtener métricas de tráfico NAT.