Problema

Al ejecutar wg‑easy dentro de un contenedor Docker en un router OpenWrt, los clientes WireGuard establecen la conexión pero pierden la capacidad de alcanzar tanto la LAN como la WAN. Las peticiones DNS (por ejemplo a 1.1.1.1) nunca llegan, y cualquier intento de ping a la puerta de enlace local devuelve Destination Port Unreachable. El síntoma típico es una interfaz tun0 activa en el cliente, tráfico visible en el contenedor, pero sin rutas de salida hacia la red del router ni hacia Internet.

Este escenario no es exclusivo de wg‑easy; cualquier contenedor que exponga una interfaz TUN y dependa del puente Docker para el enrutamiento puede quedar aislado por la configuración predeterminada del firewall y del NAT de OpenWrt.

Causa

  1. Aislamiento del puente Docker
    Docker crea una red bridge (por ejemplo wg) que, por defecto, no tiene reglas de NAT ni de reenvío hacia la zona LAN de OpenWrt. El tráfico que sale por tun0 llega al contenedor, pero nunca cruza el puente hacia br‑lan.

  2. Reglas de firewall de OpenWrt
    OpenWrt clasifica sus interfaces en zonas (lan, wan, guest, etc.). Si la interfaz virtual wg0 (creada por wg‑easy) no está asignada a una zona con permiso de reenvío, los paquetes son descartados antes de que el router pueda masqueradearlos.

  3. Falta de MASQUERADE para la subred de WireGuard
    La subred interna de wg‑easy (por ejemplo 10.42.42.0/24) necesita una regla de NAT que convierta sus direcciones en la IP del router antes de enviarlas a la WAN. Sin ella, la respuesta nunca vuelve al cliente.

  4. Configuración de DNS en el cliente
    wg‑easy suele advertir al cliente que use la IP del propio contenedor (10.42.42.1) como servidor DNS. Si el contenedor no puede reenviar consultas DNS a un resolvedor externo, la resolución falla y parece que “no hay DNS”.

  5. Parámetros de sysctl incompletos
    Algunas variables (net.ipv4.ip_forward, net.ipv4.conf.all.src_valid_mark) son necesarias para que el kernel acepte paquetes con marcas de origen válidas y los reenvíe entre interfaces. Si no se establecen, el tráfico se bloquea silenciosamente.

Solución

1. Verificar y asignar la zona de firewall

En la UI de LuCI o mediante uci, crea una zona “wg” y permite el reenvío hacia “lan” y “wan”.

uci add firewall zone
uci set firewall.@zone[-1].name='wg'
uci set firewall.@zone[-1].network='wg0'
uci set firewall.@zone[-1].input='ACCEPT'
uci set firewall.@zone[-1].output='ACCEPT'
uci set firewall.@zone[-1].forward='REJECT'

# Permitir forwarding wg → lan y wg → wan
uci add firewall forwarding
uci set firewall.@forwarding[-1].src='wg'
uci set firewall.@forwarding[-1].dest='lan'

uci add firewall forwarding
uci set firewall.@forwarding[-1].src='wg'
uci set firewall.@forwarding[-1].dest='wan'

uci commit firewall
/etc/init.d/firewall restart

2. Añadir regla NAT para la subred de WireGuard

iptables -t nat -A POSTROUTING -s 10.42.42.0/24 -o br-lan -j MASQUERADE
iptables -t nat -A POSTROUTING -s 10.42.42.0/24 -o eth0   -j MASQUERADE   # si eth0 es la WAN

Persistir la regla en /etc/firewall.user o usando uci:

uci add firewall redirect
uci set firewall.@redirect[-1].src='*'
uci set firewall.@redirect[-1].src_dport='51820'
uci set firewall.@redirect[-1].target='DNAT'
uci set firewall.@redirect[-1].dest='wg'
uci commit firewall

3. Habilitar el reenvío IP y los marcadores de origen

Asegúrate de que los sysctls están activos en el host y dentro del contenedor. En OpenWrt:

sysctl -w net.ipv4.ip_forward=1
sysctl -w net.ipv4.conf.all.src_valid_mark=1

En el docker‑compose.yml ya aparecen bajo sysctls, pero verifica que el archivo real se cargó:

sysctls:
  - net.ipv4.ip_forward=1
  - net.ipv4.conf.all.src_valid_mark=1

4. Configurar DNS para los clientes WireGuard

En la sección [Interface] del archivo de configuración generado por wg‑easy, añade:

DNS = 10.42.42.1

Si prefieres usar un resolvedor externo, habilita el reenvío de consultas DNS desde el contenedor:

iptables -A INPUT -p udp --dport 53 -j ACCEPT
iptables -A OUTPUT -p udp --sport 53 -j ACCEPT

O instala dnsmasq dentro del contenedor y apunta a 1.1.1.1 como upstream.

5. Revisar la definición de la red Docker

El puente wg debe estar conectado a la red LAN del host para que el router vea la IP del contenedor como parte de la LAN. En docker‑compose.yml:

networks:
  wg:
    driver: bridge
    enable_ipv6: true
    ipam:
      config:
        - subnet: 10.42.42.0/24

Asegúrate de que ipv4_address asignado al contenedor (10.42.42.42) no colisiona con ninguna IP estática de la LAN.

6. Reiniciar servicios

docker compose down && docker compose up -d
/etc/init.d/firewall restart

Cuándo aplicar esta solución

  • Síntomas: cliente WireGuard conectado, pero sin acceso a LAN, WAN o DNS; ping a la puerta de enlace devuelve Destination Port Unreachable; nslookup nunca alcanza servidores externos.
  • Entorno: router OpenWrt con Docker instalado, contenedor que expone WireGuard (wg‑easy, wireguard-go, etc.) y usa una red bridge personalizada.
  • Exclusiones: si el contenedor se ejecuta en modo host (no hay puente) o si la LAN ya está configurada como zona de reenvío para la interfaz wg0, la regla NAT y la zona adicional pueden ser redundantes.

Código

# 1. Crear zona wg y permitir forwarding
uci add firewall zone
uci set firewall.@zone[-1].name='wg'
uci set firewall.@zone[-1].network='wg0'
uci set firewall.@zone[-1].input='ACCEPT'
uci set firewall.@zone[-1].output='ACCEPT'
uci set firewall.@zone[-1].forward='REJECT'

uci add firewall forwarding
uci set firewall.@forwarding[-1].src='wg'
uci set firewall.@forwarding[-1].dest='lan'

uci add firewall forwarding
uci set firewall.@forwarding[-1].src='wg'
uci set firewall.@forwarding[-1].dest='wan'

uci commit firewall
/etc/init.d/firewall restart

# 2. Regla NAT para la subred WireGuard
iptables -t nat -A POSTROUTING -s 10.42.42.0/24 -o br-lan -j MASQUERADE
iptables -t nat -A POSTROUTING -s 10.42.42.0/24 -o eth0 -j MASQUERADE

# 3. Habilitar sysctls
sysctl -w net.ipv4.ip_forward=1
sysctl -w net.ipv4.conf.all.src_valid_mark=1

# 4. Reiniciar Docker y firewall
docker compose down && docker compose up -d
/etc/init.d/firewall restart

Verificación

  1. Estado de la interfaz

    ip a show wg0
    ip a show tun0   # en el cliente
    

    wg0 debe tener una IP dentro de 10.42.42.0/24.

  2. Prueba de ping a la LAN

    ping -c 3 192.168.1.1
    

    Debería responder sin Destination Port Unreachable.

  3. Resolución DNS

    nslookup google.com 10.42.42.1
    

    La respuesta debe provenir del contenedor o del upstream configurado.

  4. Comprobación de NAT
    En el router:

    iptables -t nat -L POSTROUTING -n -v | grep 10.42.42.0
    

    La regla MASQUERADE debe mostrarse con contadores incrementándose tras el ping.

  5. Firewall logs

    logread -e wg0
    

    No deberían aparecer rechazos de paquetes entre wg y lan/wan.

Notas adicionales

  • Persistencia de iptables: OpenWrt vuelve a cargar /etc/firewall.user en cada reinicio. Añade la regla NAT allí para evitar perderla.
  • Múltiples subredes: si utilizas IPv6 en wg‑easy, replica la regla MASQUERADE con -t nat -A POSTROUTING -s fdcc:ad94:bacf:61a3::/64 -j MASQUERADE.
  • Docker‑compose vs. Docker‑run: los parámetros --cap-add=NET_ADMIN y --sysctl son obligatorios; omitirlos vuelve a producir el aislamiento.
  • Depuración rápida: tcpdump -i wg0 -nn en el router muestra si los paquetes