Когда VPN-туннель разрывается или ещё не установлен, ядро продолжает маршрутизировать трафик через основной интерфейс — eth0, wlan0 или другой. Приложения не получают уведомления и работают с реальным IP-адресом. Kill switch на уровне файрвола решает проблему радикально: весь исходящий трафик, кроме явно разрешённого, отбрасывается. Ниже — рабочие наборы правил для iptables и nftables с разбором типичных ошибок.
Утечка возникает из-за того, как ядро Linux принимает решения о маршрутизации. VPN-клиент поднимает туннельный интерфейс (wg0, tun0, tap0) и добавляет маршрут по умолчанию через него. Если туннель падает, маршрут удаляется, и ядро возвращается к маршруту через физический интерфейс. Между моментом разрыва и восстановлением туннеля — или если клиент не переподключается вовсе — весь трафик идёт напрямую.
Вторая точка утечки — DNS. Даже при работающем туннеле резолвер может отправлять запросы на DNS-сервер провайдера, полученный через DHCP, минуя VPN. Это отдельная проблема, требующая дополнительных правил.
Третья — IPv6. Если в системе активен стек IPv6, а правила написаны только для iptables (IPv4), трафик уйдёт через IPv6-маршрут без ограничений.
Логика цепочки OUTPUT строится по принципу «запрещено всё, что не разрешено». Порядок правил критичен: iptables обрабатывает их сверху вниз до первого совпадения.
Замените
Для OpenVPN с tun0:
Правила iptables живут в памяти и сбрасываются при перезагрузке:
nftables — стандартный бэкенд файрвола в современных дистрибутивах. Синтаксис компактнее, а замена всего набора правил происходит атомарно: нет окна, в котором часть правил уже удалена, а новые ещё не загружены.
Ключевое отличие от iptables:
Таблица
Загрузка и сохранение:
Kill switch создаёт проблему курицы и яйца: если правила активны до установления туннеля, клиент не может подключиться к серверу. Разрешающее правило для IP сервера решает это, но есть нюанс с доменными именами.
Если VPN-сервер указан доменом, клиенту сначала нужен DNS-запрос, который будет заблокирован. Варианты:
Если в системе активен IPv6, правила iptables его не покрывают — нужен отдельный набор через ip6tables:
Если IPv6 не используется и не нужен через VPN, проще заблокировать его целиком:
В nftables таблица
Подробнее о механизмах утечки IPv6 и способах диагностики маршрутизации — в материале Утечка IPv6 через VPN: почему она возникает и как проверить маршрутизацию.
DNS-запросы могут уходить мимо туннеля, даже если остальной трафик заблокирован. Два подхода:
Блокировка DNS вне туннеля. Запретить исходящие запросы на порт 53 для всех интерфейсов, кроме VPN:
В nftables (внутри цепочки output, до правила
Принудительный DNS через туннель. Указать в
WireGuard привязан к тому сетевому пространству имён, в котором был создан интерфейс. Это позволяет изолировать контейнер или процесс: интерфейс создаётся в основном namespace с доступом в интернет, затем перемещается в namespace контейнера как единственный интерфейс. Контейнер физически не может отправить пакет мимо туннеля — у него нет другого интерфейса.
Этот подход надёжнее правил файрвола, потому что исключает саму возможность утечки на уровне маршрутизации, а не фильтрует пакеты постфактум.
Активные правила:
Тест утечки. Остановите VPN-клиент и попробуйте открыть любой сайт или выполнить
Проверка внешнего IP:
Если возвращаемый адрес совпадает с адресом VPN-сервера, а не провайдера — утечки нет.
Проверка DNS.
Правило для VPN-сервера слишком широкое. Если разрешить весь трафик на IP сервера без ограничения порта и протокола, при компрометации сервера или подмене маршрута трафик уйдёт мимо туннеля. Указывайте конкретный порт и протокол.
Забыт IPv6. Правила iptables не влияют на ip6tables. Если IPv6 активен, трафик уйдёт через него. В nftables таблица
Kill switch не переживает перезагрузку. Без сохранения правил в конфигурационный файл они теряются. Убедитесь, что правила загружаются при старте — через
Порядок правил в iptables. Если
Конфликт с другими правилами. Docker, libvirt и другие сервисы создают собственные цепочки и таблицы. В nftables используйте отдельную таблицу с собственным приоритетом, чтобы изолировать kill switch от остального файрвола.
Механика утечки
Утечка возникает из-за того, как ядро Linux принимает решения о маршрутизации. VPN-клиент поднимает туннельный интерфейс (wg0, tun0, tap0) и добавляет маршрут по умолчанию через него. Если туннель падает, маршрут удаляется, и ядро возвращается к маршруту через физический интерфейс. Между моментом разрыва и восстановлением туннеля — или если клиент не переподключается вовсе — весь трафик идёт напрямую.
Вторая точка утечки — DNS. Даже при работающем туннеле резолвер может отправлять запросы на DNS-сервер провайдера, полученный через DHCP, минуя VPN. Это отдельная проблема, требующая дополнительных правил.
Третья — IPv6. Если в системе активен стек IPv6, а правила написаны только для iptables (IPv4), трафик уйдёт через IPv6-маршрут без ограничений.
Kill switch на iptables
Логика цепочки OUTPUT строится по принципу «запрещено всё, что не разрешено». Порядок правил критичен: iptables обрабатывает их сверху вниз до первого совпадения.
Bash:
## Очистить цепочку OUTPUT (удалит все существующие правила в этой цепочке)
iptables -F OUTPUT
## Разрешить loopback — без него не работают локальные сервисы
iptables -A OUTPUT -o lo -j ACCEPT
## Разрешить подключение к VPN-серверу (замените на свои значения)
iptables -A OUTPUT -d 203.0.113.50 -p udp --dport 51820 -j ACCEPT
## Разрешить весь трафик через VPN-интерфейс
iptables -A OUTPUT -o wg0 -j ACCEPT
## Разрешить ответы на уже установленные соединения
iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
## Отбросить всё остальное
iptables -A OUTPUT -j DROP
Замените
203.0.113.50 на IP вашего VPN-сервера, 51820 — на порт и протокол (для OpenVPN обычно UDP 1194 или TCP 443), wg0 — на имя туннельного интерфейса.Для OpenVPN с tun0:
Bash:
iptables -A OUTPUT -d 203.0.113.50 -p udp --dport 1194 -j ACCEPT
iptables -A OUTPUT -o tun0 -j ACCEPT
В современных дистрибутивахiptablesчасто является обёрткой над nftables-бэкендом (iptables-nft). Правила при этом работают идентично. Проверить бэкенд можно командойiptables --version: вывод содержит(nf_tables)или(legacy).
Сохранение правил
Правила iptables живут в памяти и сбрасываются при перезагрузке:
Bash:
## Debian/Ubuntu (пакет iptables-persistent)
iptables-save > /etc/iptables/rules.v4
## RHEL/CentOS/Fedora
iptables-save > /etc/sysconfig/iptables
Эквивалент в nftables
nftables — стандартный бэкенд файрвола в современных дистрибутивах. Синтаксис компактнее, а замена всего набора правил происходит атомарно: нет окна, в котором часть правил уже удалена, а новые ещё не загружены.
Код:
table inet vpn_killswitch {
chain output {
type filter hook output priority 0; policy drop;
## Loopback
oifname "lo" accept
## Подключение к VPN-серверу
ip daddr 203.0.113.50 udp dport 51820 accept
## Трафик через туннель
oifname "wg0" accept
## Установленные соединения
ct state established,related accept
}
}
Ключевое отличие от iptables:
policy drop задаётся прямо в объявлении цепочки. Если ни одно правило не совпало, пакет отбрасывается без явного -j DROP в конце.Таблица
inet обрабатывает одновременно IPv4 и IPv6, что упрощает защиту от утечек обоих семейств.Загрузка и сохранение:
Bash:
## Загрузить из файла
nft -f /etc/nftables.conf
## Сохранить текущий набор правил
nft list ruleset > /etc/nftables.conf
Начальное подключение к VPN-серверу
Kill switch создаёт проблему курицы и яйца: если правила активны до установления туннеля, клиент не может подключиться к серверу. Разрешающее правило для IP сервера решает это, но есть нюанс с доменными именами.
Если VPN-сервер указан доменом, клиенту сначала нужен DNS-запрос, который будет заблокирован. Варианты:
- Указать в правиле конкретный IP-адрес сервера. Для WireGuard это стандартная практика: конфигурация содержит IP endpoint напрямую.
- Использовать скрипт, который сначала резолвит имя, подставляет IP в правило и только потом включает блокировку.
- Временно разрешить DNS до подключения, затем активировать полный kill switch.
Утечка IPv6
Если в системе активен IPv6, правила iptables его не покрывают — нужен отдельный набор через ip6tables:
Bash:
ip6tables -F OUTPUT
ip6tables -A OUTPUT -o lo -j ACCEPT
ip6tables -A OUTPUT -o wg0 -j ACCEPT
ip6tables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
ip6tables -A OUTPUT -j DROP
Если IPv6 не используется и не нужен через VPN, проще заблокировать его целиком:
Bash:
ip6tables -A OUTPUT -j DROP
В nftables таблица
inet покрывает оба семейства, но только если правила не ограничены явно ip daddr (IPv4) или ip6 daddr (IPv6). Правило oifname "wg0" accept в таблице inet работает для обоих.Подробнее о механизмах утечки IPv6 и способах диагностики маршрутизации — в материале Утечка IPv6 через VPN: почему она возникает и как проверить маршрутизацию.
Утечка DNS
DNS-запросы могут уходить мимо туннеля, даже если остальной трафик заблокирован. Два подхода:
Блокировка DNS вне туннеля. Запретить исходящие запросы на порт 53 для всех интерфейсов, кроме VPN:
Bash:
## iptables: вставка в начало цепочки OUTPUT
iptables -I OUTPUT -p udp --dport 53 ! -o wg0 -j DROP
iptables -I OUTPUT -p tcp --dport 53 ! -o wg0 -j DROP
В nftables (внутри цепочки output, до правила
oifname "wg0" accept):
Код:
udp dport 53 oifname != "wg0" drop
tcp dport 53 oifname != "wg0" drop
Принудительный DNS через туннель. Указать в
/etc/resolv.conf или в настройках NetworkManager / systemd-resolved DNS-сервер VPN-провайдера. Это не заменяет блокировку, но гарантирует, что легитимные запросы идут через туннель.Альтернатива: сетевые пространства имён
WireGuard привязан к тому сетевому пространству имён, в котором был создан интерфейс. Это позволяет изолировать контейнер или процесс: интерфейс создаётся в основном namespace с доступом в интернет, затем перемещается в namespace контейнера как единственный интерфейс. Контейнер физически не может отправить пакет мимо туннеля — у него нет другого интерфейса.
Этот подход надёжнее правил файрвола, потому что исключает саму возможность утечки на уровне маршрутизации, а не фильтрует пакеты постфактум.
Проверка результата
Активные правила:
Bash:
iptables -L OUTPUT -n -v --line-numbers
## или
nft list chain inet vpn_killswitch output
Тест утечки. Остановите VPN-клиент и попробуйте открыть любой сайт или выполнить
curl. Если kill switch работает, соединение не установится. Поднимите VPN и повторите — трафик должен пойти.Проверка внешнего IP:
Bash:
curl -4 ifconfig.me
Если возвращаемый адрес совпадает с адресом VPN-сервера, а не провайдера — утечки нет.
Проверка DNS.
resolvectl status (systemd-resolved) или cat /etc/resolv.conf покажет, какой DNS-сервер используется. Запрос должен идти через туннельный интерфейс.Типичные ошибки
Правило для VPN-сервера слишком широкое. Если разрешить весь трафик на IP сервера без ограничения порта и протокола, при компрометации сервера или подмене маршрута трафик уйдёт мимо туннеля. Указывайте конкретный порт и протокол.
Забыт IPv6. Правила iptables не влияют на ip6tables. Если IPv6 активен, трафик уйдёт через него. В nftables таблица
inet покрывает оба семейства, но только при корректных правилах.Kill switch не переживает перезагрузку. Без сохранения правил в конфигурационный файл они теряются. Убедитесь, что правила загружаются при старте — через
iptables-persistent, nftables.service или unit-файл.Порядок правил в iptables. Если
-j DROP стоит раньше разрешающих правил, они никогда не сработают. Проверяйте порядок через --line-numbers.Конфликт с другими правилами. Docker, libvirt и другие сервисы создают собственные цепочки и таблицы. В nftables используйте отдельную таблицу с собственным приоритетом, чтобы изолировать kill switch от остального файрвола.
