Kill-switch після переходу з Wi-Fi на мобільну мережу: що ламається
Kill-switch пишеться під назви інтерфейсів, які замінює зміна мережі. Перевірте зовнішній шлях, родини адрес і резолвер, перш ніж йому довіряти.
Однієї миті ноутбук у офісному Wi-Fi, наступної він під'єднаний до телефона, і індикатор тунелю знову зелений, перш ніж ви закінчите перемикати вікна. Зелене світло повідомляє лише те, що тунель перепідключився; що робила політика маршрутизації та блокування в цей проміжок — інше питання. Ця політика є набором умов зіставлення, скомпільованих під час останнього встановлення, і називає інтерфейс, адресу та родину адрес, які перехід щойно видалив. Тому той самий перехід дає три результати, що виглядають однаково: блокування триває, політика блокує власне відновлення, або відкривається шлях, який ніщо не покриває.
Ніщо на екрані не розрізняє ці три стани, тож питайте, до яких ідентифікаторів були прив'язані правила, а не якого кольору індикатор. У Linux правило може називати інтерфейс або ipset, заповнений з нього; в Android службі VpnService можна вказати через setUnderlyingNetworks, за якими нижчими мережами слідувати. Кожна прив'язка вказує на канал, який ось-ось буде замінено, і кожну треба переоцінити після цього.
Що перехід насправді переписує
Коли старий канал зникає, ядро видаляє його адреси, відкликає маршрути, що йшли через нього, і заново будує вибір адреси джерела з того, що лишилося. Маршрут за замовчуванням переходить на новий шлюз, а індекс інтерфейсу, на який посилалися старі правила, зник або перевикористаний для іншого пристрою. Резолвери на канал теж оновлюються, хоча й не тієї ж миті, що маршрути. Усе це відбувається нижче клієнта VPN, тож клієнт, який стежить лише за станом власного тунелю, нічого з цього не бачить.
Проблема в тому, що правило кодує конкретний ідентифікатор, а не намір за ним. Правило, яке відкидає все, що не виходить через інтерфейс тунелю, кодує ім'я інтерфейсу; правило, яке дозволяє кінцеву точку, кодує адресу, яку нова мережа може спрямувати в інше місце. Коли ідентифікатор зникає, правило не стає видимо неправильним: воно або перестає збігатися з чимось, або починає збігатися з неправильним.
Перебудувати зовнішній тунель, не пробиваючи дірку
Зовнішній транспорт був прив'язаний до старої адреси джерела, тож має відкрити новий сокет з нової. Коли кінцева точка — ім'я хоста, а не буквальна адреса, пошук має встигнути, поки блокування ще діє. Це пастка порядку: політика, достатньо строга, щоб відкинути все, крім кінцевої точки, відкине й пошук, потрібний для її знаходження, якщо тільки розв'язання не відбудеться до зняття блокування або не буде пронесене через ще живий тунель. Обидва порядки можуть працювати, але лише якщо клієнт знає, у якому він.
Клієнти різняться тут так, що це важливо під час діагностики. Одні спершу розв'язують, а потім ставлять правила, тож перехід коштує видимої перерви і потім відновлюється сам; інші тримають блокування і покладаються на тунель, що вижив, аби пронести пошук, а це повністю провалюється, коли тунель помер разом із каналом. Додавання широкого прямого маршруту за замовчуванням для відновлення зв'язку завжди пропускає тест і завжди залишає той витік, який ви запобігали.
Маршрут, що досі вказує на старий шлюз, дає те саме застрягле відновлення з іншої причини, тож прочитайте таблицю, перш ніж звинувачувати клієнта. Команди нижче називають інтерфейс і адресу джерела, які ядро справді використало б, і це вирішує в одному рядку, чи ще встановлено старий шлях.
ip route get 1.1.1.1
ip -6 route get 2606:4700:4700::1111Коли новий канал приносить IPv6, а правила ні
Мобільні та гостьові мережі часто видають глобальний префікс IPv6, навіть якщо дротова LAN офісу ніколи цього не робила. Політика, написана лише як правила IPv4, звичайна форма більшості саморобних рецептів iptables і nftables, не покриває нічого з цього. Тунель лишається під'єднаним, застосунки з доступністю IPv6 виходять повз нього, а індикатор не може показати різницю, бо тунель справді піднятий. Дзеркальний випадок так само звичайний: набір правил лише для IPv6 нічого не робить у мережі, яка не пропонує v6.
Порівняйте, що пропонує канал, із тим, що згадує політика, з обох боків переходу. Ці команди друкують ті самі факти, з якими працює клієнт:
ip -4 route show default
ip -6 route show default
sudo nft list ruleset | grep -c 'ip6 'Якщо друга команда друкує маршрут за замовчуванням, а третя повертає нуль, новий канал може вивести машину назовні через IPv6, і жодне правило за цим не стежить. Запустіть усі три знову після перемикання, бо цікаве число — різниця між двома запусками. Якщо друга нічого не друкує, діє зворотний випадок і будь-яка політика, важка на v6, там лише оздоба.
Резолвер міняє руки посеред сесії
У сучасних системах резолвери прив'язані до каналів, тож новий канал приходить із новим набором резолверів і, можливо, з іншим поданням split-DNS. systemd-resolved тримає по одній області на канал і вибирає між ними за маршрутом, а macOS читає області видимості резолверів із динамічного сховища і поважає порядок інтерфейсів. Клієнт, який встановлює власний резолвер, конкурує з резолвером каналу, і правила пріоритету, за якими ніхто не стежить, вирішують переможця. Тоді розв'язання імен може зламатися, хоча фільтрація пакетів робить саме те, для чого її написано.
Читайте активний резолвер, а не налаштований. На хості systemd підкоманда status показує, з якого каналу прийшла кожна область і чи діє ім'я private-DNS:
resolvectl status --no-pager
resolvectl query intranet.example.comЯкщо запит виходить через фізичний канал, а не через тунель, клієнт програв змагання пріоритетів, і жодне налаштування брандмауера не виправить іменування. Порівняйте шлях запиту з обох боків переходу, щоб побачити, який змістився. Ім'я, що розв'язується в приватну адресу, яку нова мережа не може маршрутизувати, — третій випадок, і він виявляється як тайм-аут, а не як помилка.
Відпрацюйте перехід і збережіть запис
Відтворіть зміну навмисно, а не чекайте на неї. Підніміть тунель на першому каналі, запустіть тривалу передачу та повторний пошук, потім змініть канал і подивіться, що виживе. Запишіть інтервал між зникненням старого каналу та повідомленням тунелю про підключення, бо саме в цьому вікні політика або втрималася, або ні. Прогоніть послідовність і у зворотному напрямку, щоб результат не був однобічним.
Винятки потребують власного проходу, бо обхід, виправданий у старій мережі, не виправданий автоматично в новій. Принтер, NAS або captive portal у домашній LAN не мають відповідника в мобільній мережі, і виняток, який до них діставався, стає правилом без відповідника.
Запишіть назви інтерфейсів, пропоновані родини, чинний резолвер і тривалість перерви для кожного напрямку. Перепідключення доводить, що транспорт відновився, а не що блокування втрималося в проміжку, і це окремі твердження для заявки. Із цим записом наступний перехід порівнюють із відомим станом, а не з пам'яттю.