Гачок працює першого разу, а після трьох повторних підключень ланцюг містить три копії того самого правила. Ніщо не впало гучно; скрипт просто щоразу робив те, що йому казали. Гачок VPN — це операція налаштування, яка може виконуватися більше одного разу, тому їй потрібні власник, бажаний стан і серіалізована зміна. Ідіть у цьому порядку, і дублікати припиняться.

Дайте правилам власника, перш ніж писати хоч одне

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

iptables -N VPN_HOOK 2>/dev/null || true
iptables -C INPUT -j VPN_HOOK 2>/dev/null || iptables -I INPUT -j VPN_HOOK
iptables -S VPN_HOOK

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

Перевіряйте бажаний стан замість повторного додавання

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

iptables -C VPN_HOOK -i tun0 -p tcp --dport 443 -j ACCEPT ||
  iptables -A VPN_HOOK -i tun0 -p tcp --dport 443 -j ACCEPT

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

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

Серіалізуйте зміну замість надії, що вона втримається

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

flock /run/vpn-hook.lock /usr/local/sbin/vpn-firewall-apply
nft -f /etc/nftables/vpn-hook.nft

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

Демонтуйте лише те, що належить вам

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

iptables -D VPN_HOOK -i tun0 -p tcp --dport 443 -j ACCEPT
iptables -F VPN_HOOK
ip -brief link show type tun

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

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

Схожі статті