Запис про відключення надходить після того, як друге підключення під тим самим іменем уже почалося, і обробник, що оновлює найновіший рядок для цього користувача, закриває не ту сесію. Помилка не в порівнянні часових позначок. Помилка в припущенні, що ім’я плюс ціла секунда ідентифікують одну сесію. Події хуків описують підключення, а підключенню потрібен ключ, що належить лише йому.

Почніть із того, що насправді означають змінні відключення. Лише тоді вибір ідентифікатора має сенс.

Що насправді несе хук відключення

У Community OpenVPN 2.6 довідка щодо хуків time_unix визначає як момент, коли клієнт підключився; він встановлюється перед запуском хука підключення і не перевизначається, коли хук відключення спрацьовує пізніше. time_duration дає тривалість цієї сесії в секундах, тож кінець — це приблизно час підключення плюс тривалість. Обидва — цілі секунди, і жоден не є моментом, коли ваша база даних обробила подію. Саме розділення цих трьох часів дозволяє кореляції триматися, а не просто виглядати правильно під час тестування.

Ця арифметика варта запису, бо це єдиний час завершення, який пропонує сам хук. Затримка обробки та розбіжності годинників між хостами все ще залишаються поза нею.

Чому очевидна пара не є унікальною

Поєднання імені з часом підключення звужує пошук і все одно не ідентифікує сесію. Клієнт може перепідключитися тієї ж секунди, тож дві сесії можуть ділити обидва значення. Два екземпляри демона або заміна сервера, що зберігає ту саму конфігурацію, можуть створити цю пару з різних процесів. Адреси тунелів перевикористовуються з тієї ж причини, що робить їх атрибутами сесії, а не її ідентифікаторами.

Режим відмови тихий. Пізнє відключення першої сесії збігається з найновішим рядком і завершує виділення, яке друга сесія ще використовує, тож користувач зазнає випадкової втрати доступу, якої не спричинив. Кількість рядків, що потім виглядає правильно, робить проблему важчою для виявлення.

Звідки береться стабільна ідентичність

Інтерфейс керування призначає кожному клієнту ідентифікатор екземпляра клієнта, і цей ідентифікатор обмежений екземпляром демона, що його видав. Робочий зовнішній ключ поєднує стабільний ідентифікатор сервера, епоху, що змінюється під час запуску демона, і той клієнтський ID. Епоха важлива, бо ідентифікатори перезапускаються разом із процесом, а голий ID процесу може згодом бути повторно використаний чимось не пов’язаним.

Змінні середовища, встановлені всередині дочірнього скрипта підключення, не переходять до пізнішого скрипта відключення батьківського демона, бо ці скрипти виконуються як окремі процеси з власними середовищами. Тож генерація UUID під час підключення — це половина рішення. Бік відключення повинен уміти назвати той самий UUID, а це означає, що ідентичність має жити там, куди можуть дістатися обидві події.

Зробіть завершення умовним та ідемпотентним

Завершуйте за власником сесії, а не за користувачем. Обробник, що видаляє будь-яке виділення, яке зараз належить імені, зрештою видалить не те, і єдиний захист — перевірити, що видалюваний запис і є запис, який описує ця подія.

Інтерфейс керування приймає одного підключеного клієнта керування за раз, тож інтегруйтеся з тим, хто вже володіє ним, замість приєднувати другого спостерігача, що конкурує за той самий сокет.

Обробляйте повторювані події, не змінюючи результат двічі, і зберігайте непарні події для звірки, а не відкидайте їх. Зворотний виклик відключення має задокументовані умови життєвого циклу і може ніколи не прийти після збою чи жорсткої зупинки, тож дизайну потрібен спосіб подати сесію, подія завершення якої відсутня.

Перевірте незручні випадки навмисно: два перепідключення протягом однієї секунди, два екземпляри демона, підключення, обробка якого не вдалася, і перезапуск демона посеред сесії. Ключ, що витримує всі чотири, вартий довіри.

Схожі статті