Eventos de hook do OpenVPN: por que nome e carimbo de tempo não são uma chave
Um hook de desconexão relata o horário da conexão, não o da desconexão. Junto com o nome de usuário, pode encerrar a sessão errada; possua uma identidade em vez disso.
Um registro de desconexão chega depois de uma segunda conexão com o mesmo nome já ter começado, e um manipulador que atualiza a linha mais recente para esse usuário fecha a sessão errada. O erro não é a comparação dos carimbos de tempo. É a suposição de que um nome mais um segundo inteiro identificam uma sessão. Eventos de hook descrevem conexões, e uma conexão precisa de uma chave que pertença só a ela.
Comece pelo que as variáveis de desconexão realmente significam. Só então a escolha do identificador faz sentido.
O que o hook de desconexão realmente carrega
No Community OpenVPN 2.6, a referência de hooks time_unix define como o momento em que o cliente se conectou; ele é definido antes de o hook de conexão ser executado e não é redefinido quando o hook de desconexão dispara mais tarde. time_duration dá a duração dessa sessão em segundos, então o fim é aproximadamente o horário da conexão mais a duração. Ambos são segundos inteiros, e nenhum é o momento em que seu banco de dados processou o evento. Manter esses três tempos separados é o que faz uma correlação se sustentar, em vez de apenas parecer correta durante os testes.
Essa aritmética merece ser registrada, porque é o único horário de término que o próprio hook oferece. O atraso de processamento e as diferenças de relógio entre hosts ainda ficam de fora.
Por que o par óbvio não é único
Emparelhar o nome com o horário da conexão estreita a busca e ainda não identifica uma sessão. Um cliente pode se reconectar dentro do mesmo segundo, então duas sessões podem compartilhar ambos os valores. Duas instâncias do daemon, ou uma substituição de servidor que mantém a mesma configuração, podem produzir esse par a partir de processos diferentes. Os endereços de túnel são reciclados pelo mesmo motivo, o que os torna atributos de uma sessão, não identificadores dela.
O modo de falha é silencioso. Uma desconexão tardia da primeira sessão corresponde à linha mais recente e encerra uma alocação que a segunda sessão ainda usa, então o usuário sofre uma perda de acesso aleatória que não provocou. Uma contagem de linhas que depois parece correta torna o problema mais difícil de detectar.
De onde vem uma identidade estável
A interface de gerenciamento atribui a cada cliente um identificador de instância de cliente, e esse identificador é limitado à instância do daemon que o emitiu. Uma chave externa viável combina um identificador de servidor estável, uma época que muda quando o daemon inicia e esse ID de cliente. A época importa porque os identificadores reiniciam com o processo, e um ID de processo puro pode ser reutilizado mais tarde por algo não relacionado.
Variáveis de ambiente definidas dentro de um script de conexão filho não viajam para o script de desconexão posterior do daemon pai, porque esses scripts rodam como processos separados com seus próprios ambientes. Por isso, gerar um UUID no momento da conexão é meia solução. O lado da desconexão precisa conseguir nomear o mesmo UUID, o que significa que a identidade deve viver em algum lugar que ambos os eventos alcancem.
Torne a aposentadoria condicional e idempotente
Aposente por dono da sessão, não por usuário. Um manipulador que exclui qualquer alocação que atualmente pertença a um nome acabará excluindo a errada, e a única defesa é verificar que o registro removido é o registro que este evento descreve.
A interface de gerenciamento aceita um cliente de gerenciamento conectado por vez, então integre-se com quem já a possui em vez de anexar um segundo observador que disputa o mesmo socket.
Trate eventos repetidos sem mudar o resultado duas vezes, e guarde eventos não emparelhados para reconciliação em vez de descartá-los. Um callback de desconexão tem condições de ciclo de vida documentadas e pode nunca chegar após uma falha ou parada forçada, então o design precisa de uma forma de representar uma sessão cujo evento final falta.
Teste os casos incômodos de propósito: duas reconexões dentro de um segundo, duas instâncias do daemon, uma conexão cujo processamento falhou e um reinício do daemon no meio de uma sessão. Uma chave que sobrevive a todos os quatro é uma chave digna de confiança.