Cuando un script client-connect sale con un código distinto de cero, OpenVPN rechaza al cliente, y los operadores suelen suponer que el script client-disconnect correspondiente lo ordena todo. A menudo nunca se ejecuta, porque el procesamiento de disconnect depende de lo que la fase connect logró completar. Tratar el rechazo y la reversión como un solo mecanismo es lo que convierte un inicio de sesión fallido en una regla de cortafuegos filtrada o en un registro huérfano. Son dos obligaciones separadas, y solo una de ellas es automática. Lea la ruta de salida propia del hook antes de culpar al servidor por dejar estado atrás.

Una salida distinta de cero rechaza al cliente; no deshace su trabajo

El estado de salida de client-connect es una decisión de admisión. Cero significa admitir; cualquier otra cosa significa derribar la instancia antes de que esté plenamente establecida. Nada en ese contrato inspecciona lo que el script ya hizo en el mundo exterior. Si el script añadió una regla de iptables, escribió una fila o incrementó un contador, esos cambios sobreviven al rechazo.

La misma asimetría se aplica a los complementos, donde varias funciones connect pueden ejecutarse para una sola instancia. Un fallo posterior no cancela retroactivamente un éxito anterior, y la función anterior no tiene forma de saber que una función hermana falló.

El rechazo es automático, la limpieza no

El manual de OpenVPN 2.6 vincula el procesamiento de client-disconnect con que el procesamiento de connect anterior haya tenido éxito. Una excepción documentada cubre las cadenas en cascada: si un paso connect ya devolvió éxito, los pasos disconnect correspondientes se ejecutan igualmente cuando se derriba la instancia, aunque un paso connect posterior haya fallado. Esa excepción es estrecha y depende de cuántas devoluciones de llamada estén configuradas, por eso probar un solo script de shell no le dice nada sobre una cadena.

Trace la configuración real antes de diseñar en torno a ella: qué script, qué complemento y en qué orden se ejecutan. La tabla siguiente enumera lo que deja atrás una configuración a medio terminar.

What the hook did before failingWhat the client seesWhat stays behind
Created a firewall entryA rejection, with no tunnelA rule matching an address nobody owns
Wrote a state rowA rejection, with no tunnelA record of a session that never started
Allocated a tunnel addressA rejection, with no tunnelAn address marked busy until something releases it

Cada fila termina igual para el cliente y de forma distinta para el host. El cliente no puede saber cuál de ellas ocurrió, así que el hook debe deshacer lo que hizo antes de devolver el estado de fallo.

Compense dentro del script que falló

Valide todo lo que pueda antes de tocar estado compartido: campos del certificado, disponibilidad de la dirección, el grupo al que pertenece el cliente. El rechazo más barato es el que ocurre antes de cualquier asignación. Una vez que la configuración abarca varias operaciones externas, lleve un registro de cuáles se completaron y deshaga las en orden inverso antes de devolver el estado de fallo.

Un caso concreto: el script crea una entrada de cortafuegos por cliente y luego falla al escribir la fila de estado que la describe. La rama que falla debería eliminar la entrada que acaba de crear y después salir con un código distinto de cero. Esperar un evento disconnect que quizá nunca se programe es como una regla sobrevive a la sesión para la que estaba pensada.

Haga la compensación idempotente y con propietario

La limpieza debe ser segura de ejecutar dos veces y precisa sobre qué puede borrar. Un evento tardío de un intento anterior no debería eliminar la asignación de una sesión más nueva solo porque ambas comparten un nombre común o una dirección de túnel reciclada. Registre el identificador o el id de operación concreto junto con el recurso, y borre por esa clave en lugar de por el nombre. Las transacciones de base de datos solo ayudan con los cambios propios de la base de datos; una regla de cortafuegos fuera de ese límite necesita un paso de compensación explícito.

Suponga que el proceso puede morir entre el cambio y la limpieza, y añada un reconciliador que compare el estado externo con la lista de clientes admitidos según un calendario.

Pruebe los tres caminos que realmente difieren

Practique tres casos: un rechazo antes de que se asigne nada, uno después de una asignación parcial y una sesión admitida normal que luego termina. Esos tres producen líneas de registro distintas y residuos distintos. Verifique el estado externo después de cada uno, no solo el mensaje de fallo del cliente, porque el fallo del cliente es el resultado esperado en dos de los tres.

Registre la fase del hook, el estado de salida y qué se asignó o liberó, sin las credenciales. Un registro de hook que nombra el recurso que creó es lo que hace que una regla filtrada sea localizable seis meses después.

Artículos relacionados