Ваш застосунок продовжує працювати, поки тунель помирає. Wi-Fi ще ввімкнено, інтернет ще відповідає, а приватний API, від якого залежить застосунок, став недоступним. Подія .NET, до якої тут зазвичай звертаються, NetworkAvailabilityChanged, зовсім не описує цю ситуацію. Вона повідомляє про зміну сукупного стану машини, і цей стан може залишатися істинним під час відключення й повторного підключення VPN. Спостерігач має стежити за клієнтом, що володіє тунелем, а не за зведенням мережевого стеку.

Доступність — це агрегат, а не ваш тунель

NetworkAvailabilityChanged спрацьовує, коли змінюється сукупна відповідь машини про доступність, і ця відповідь враховує будь-який активний інтерфейс, крім loopback або тунелю.

Обрив VPN при ввімкненому Wi-Fi нічого не змінює з того, що вона вимірює, тому подія може мовчати саме під час збою, який вас турбує.

Потік стану належить клієнту, що володіє тунелем

Визначте, який компонент володіє з'єднанням: GUI або служба OpenVPN, OpenVPN Connect чи процес, який запустив і контролює ваш застосунок. Це різні власники з різними підтримуваними інтерфейсами, і спостерігач має використовувати той, що відповідає розгортанню. Для процесу OpenVPN, з яким вам дозволено інтегруватися, інтерфейс керування повідомляє поточний стан, історію та сповіщення в реальному часі. Аналізуйте документовані структуровані повідомлення замість пошуку слова connected у локалізованому підписі GUI.

Канал керування — привілейована поверхня керування, тому не відкривайте новий слухач і не сперечайтеся з GUI за канал, який він уже тримає.

Захисти поколінь зупиняють застарілі результати

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

Розгляньмо перевірку, розпочату під час з'єднання A. Тунель падає, починається з'єднання B, а потім надходить запізнілий успіх HTTP від A. Без захисту цей старий результат робить екран зеленим, поки B ще проходить автентифікацію. Скасування зменшує марну роботу, а перевірка покоління не дає застарілому завершенню стати поточним станом:

var generation = _generation;
var reachable = await ProbeAsync(token);
if (generation != _generation) return;
Publish(reachable);

Застосуйте те саме правило навколо надсилання в UI, бо маршалізоване оновлення може прийти після того, як покоління пішло далі.

Ініціалізація та повторне підключення — один життєвий цикл

Розглядайте перше підключення, повторне підключення й втрату каналу як один життєвий цикл з одним набором правил:

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

Втрачений канал означає «невідомо», а не «відключено». Повідомляти про невідоме чесніше, ніж про відключення, яке ви не можете довести, і це не дає логіці повторних спроб діяти за здогадом.

Повідомляйте докази, а не логічне значення

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

Пошук публічної IP-адреси не є доказом того, що приватний API використав потрібний маршрут, тому не подавайте це як доказ.

Тримайте примус поза спостерігачем

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

Тоді питання про UI стає простим: покажіть те, що відомо, разом з його віком, і кажіть «невідомо», коли це так.

Схожі статті