El hook funciona la primera vez, y tras tres reconexiones la cadena contiene tres copias de la misma regla. Nada falló ruidosamente; el script simplemente hizo lo que se le dijo cada vez que se ejecutó. Un hook de VPN es una operación de configuración que puede ejecutarse más de una vez, así que necesita un propietario, un estado deseado y un cambio serializado. Sigue ese orden y los duplicados se detienen.

Dale un propietario a las reglas antes de escribir ninguna

Decide qué cadena o tabla posee esta integración antes de escribir una sola regla. Una cadena dedicada que el hook crea y vacía es la forma más clara de propiedad, porque la limpieza puede después eliminar esa cadena sin tocar la política de nadie más. Si debes añadir reglas a una cadena compartida, márcalas con un comentario para que una búsqueda posterior encuentre exactamente las tuyas. La propiedad es lo que hace seguro el desmontaje, y el desmontaje es la parte que falla en silencio.

iptables -N VPN_HOOK 2>/dev/null || true
iptables -C INPUT -j VPN_HOOK 2>/dev/null || iptables -I INPUT -j VPN_HOOK
iptables -S VPN_HOOK

Un comentario aquí no es decoración. Es la diferencia entre eliminar tu regla y eliminar una regla que da la casualidad de que se parece, y sobrevive en el conjunto de reglas guardado. Escribe el comentario una vez en una variable y usa la misma cadena en cada comando que añada, pruebe o elimine.

Prueba el estado deseado en lugar de añadir otra vez

Escribe qué debe existir mientras el túnel está levantado: qué interfaces, qué origen y destino, qué protocolo y qué puerto. Luego expresa cada regla como una prueba seguida de una inserción, para que una segunda ejecución encuentre la regla y no haga nada. El comando de comprobación termina con cero cuando la regla existe, y esa es exactamente la señal que necesita el script. La referencia de iptables documenta las formas de comprobación, adición e inserción que se usan aquí.

iptables -C VPN_HOOK -i tun0 -p tcp --dport 443 -j ACCEPT ||
  iptables -A VPN_HOOK -i tun0 -p tcp --dport 443 -j ACCEPT

Ten cuidado con la posición. Comprobar una regla e insertarla al principio puede mover un permiso delante de una denegación deliberada que antes iba primero, así que la segunda ejecución cambia el comportamiento aunque no añada nada. Añade al final dentro de tu propia cadena y controla el orden por dónde se sitúa el salto a esa cadena. Si el orden importa dentro de la cadena, reconstruye la cadena a partir de una lista conocida en lugar de parchearla regla por regla.

Detente si la regla que necesitas no se puede expresar como una prueba estable. Eso suele significar que el estado deseado sigue siendo vago.

Serializa el cambio en lugar de esperar que aguante

Dos invocaciones del hook pueden solaparse, y ambas pueden ver una regla ausente antes de que ninguna la añada. La opción de espera de xtables hace que un comando espere el bloqueo, pero no hace atómica una secuencia de comprobar-y-añadir entre dos comandos. Serializa toda la operación con tu propio bloqueo, o usa un gestor que aplique un lote declarado como una sola transacción. nftables puede hacer esto último, y es una razón para preferirlo en hosts donde está disponible.

flock /run/vpn-hook.lock /usr/local/sbin/vpn-firewall-apply
nft -f /etc/nftables/vpn-hook.nft

Sea cual sea el mecanismo que elijas, mantén la sección crítica corta y hazla reentrante: un hook que falla a mitad de camino debería ser seguro de volver a ejecutar. Registra el resultado para que una ejecución repetida sea visible en el journal en lugar de deducirse del recuento de reglas. Si dos supervisores distintos pueden invocar el hook, arréglalo primero; ningún bloqueo repara dos propietarios.

Desmonta solo lo que es tuyo

El desmontaje tiene que distinguir una conexión de reemplazo de la que retiró, y tiene que sobrevivir a un evento que nunca recibe una limpieza ordenada. Elimina con los mismos identificadores que usaste para añadir, y nunca vacíes una cadena compartida para simplificar la limpieza. Tras una parada dura, ejecuta una pasada de conciliación en el siguiente arranque en lugar de suponer que el hook de bajada se ejecutó.

iptables -D VPN_HOOK -i tun0 -p tcp --dport 443 -j ACCEPT
iptables -F VPN_HOOK
ip -brief link show type tun

La vida útil de la interfaz también forma parte del diseño. Una reconexión puede crear un dispositivo con un nombre distinto, así que una regla que fija el nombre del segundo túnel queda obsoleta mientras aún parece válida. Prefiere la identidad que expone la propia conexión y vuelve a leerla en cada ejecución. Prueba los tres eventos por separado: una conexión repetida, una conexión de reemplazo y un apagado. El tráfico permitido debe sobrevivir a los tres sin duplicados, y los servicios no relacionados deben quedar intactos.

El recorrido termina cuando el hook tiene un propietario, una prueba para cada regla que añade, un camino serializado y un desmontaje que elimina solo su propio estado. Un hook que es seguro de ejecutar dos veces vale más que un hook que es ingenioso una vez.

Artículos relacionados