При настройке виртуальной частной сети для перенаправления всего исходящего трафика (0.0.0.0/0 и ::/0) каждый пакет, создаваемый операционной системой, должен направляться в зашифрованный туннель. Такая конфигурация порождает фундаментальную архитектурную проблему, известную как рекурсивная маршрутизация (routing recursion). Зашифрованные данные должны передаваться через физический интерфейс с помощью внешнего сокета UDP. Если ядро системы отправляет весь сетевой трафик в туннельный интерфейс, внешний UDP-пакет возвращается обратно в драйвер туннеля, вызывая бесконечный цикл инкапсуляции.
Классические протоколы, такие как OpenVPN или IPsec, решали эту проблему добавлением статических маршрутов к публичному IP-адресу VPN-сервера через физический шлюз. Однако этот метод ненадежен при переключении между сетями Wi-Fi и сотовой связью, смене динамических адресов или использовании балансировщиков нагрузки Anycast.
WireGuard решает проблему рекурсивных петель в Linux с помощью меток брандмауэра (fwmark), параметра сокета SO_MARK и базы данных правил маршрутизации (Routing Policy Database, RPDB).
Дилемма рекурсивной маршрутизации в туннелях
Чтобы понять необходимость маршрутизации по правилам, рассмотрим путь сетевого пакета на рабочей станции Linux с активным интерфейсом WireGuard wg0.
Когда приложение отправляет TCP-сегмент удаленному серверу 203.0.113.50, сетевой стек ядра выбирает выходной интерфейс. Если маршрут по умолчанию указывает на wg0 (ip route add default dev wg0), ядро направляет пакет в туннель. Драйвер WireGuard обрабатывает пакет через таблицу Cryptokey Routing, шифрует его с помощью ChaCha20-Poly1305 и помещает во внешний UDP-пакет, адресованный серверу 198.51.100.10:51820.
Пакет приложения (Назначение: 203.0.113.50)
│
▼
Таблица FIB ядра: default dev wg0
│
▼
Драйвер WireGuard (wg0): Инкапсуляция в UDP (Назначение: 198.51.100.10:51820)
│
▼
Таблица FIB ядра: Снова совпадение default dev wg0! ──> ПЕТЛЯ МАРШРУТИЗАЦИИ
В момент, когда UDP-сокет WireGuard передает этот пакет сетевому стеку Linux, ядро повторно обращается к базе данных пересылки (Forwarding Information Base, FIB). Поскольку маршрут по умолчанию указывает на wg0, внешний UDP-пакет снова направляется в wg0. Драйвер туннеля пытается повторно зашифровать зашифрованный пакет, очередь сокета переполняется, размер пакета превышает MTU, и ядро отбрасывает данные. Связь полностью прерывается.
База данных правил RPDB и архитектура двух таблиц
Ядро Linux поддерживает развитую маршрутизацию с помощью базы данных правил маршрутизации (RPDB), управляемой утилитой ip rule. Вместо единой статической таблицы RPDB позволяет сопоставлять пакеты с различными независимыми таблицами на основе адреса источника, интерфейса, типа службы или метаданных.
Стандартная конфигурация Linux включает три системные таблицы:
local(таблица 255): локальные адреса и широковещательный трафик.main(таблица 254): стандартные маршруты физических интерфейсов и шлюзов DHCP.default(таблица 253): резервная таблица для специальных политик.
Инструменты управления WireGuard (например, wg-quick) не перезаписывают шлюз по умолчанию в таблице main. Вместо этого создается отдельная таблица маршрутизации — обычно с идентификатором 51820 — куда помещается маршрут по умолчанию через wg0:
# Создание маршрута по умолчанию в выделенной таблице
ip route add default dev wg0 table 51820
ip -6 route add default dev wg0 table 51820
Разделение туннельного маршрута и физического шлюза позволяет операционной системе поддерживать два независимых пространства маршрутизации. Главная задача состоит в том, чтобы направлять прикладной трафик в таблицу 51820, а транспортным сокетам WireGuard разрешать обращение к таблице main.
Маркировка сокетов через SO_MARK и проверка fwmark
Связующим звеном выступает метка брандмауэра (fwmark). В ядре Linux структура каждого сетевого буфера (struct sk_buff) содержит 32-битное поле skb->mark. Эта метка существует исключительно в оперативной памяти ядра, не передается по сети и не модифицирует IP-заголовки.
При инициализации внешнего UDP-сокета драйвер WireGuard устанавливает опцию SO_MARK через системный вызов POSIX:
int fwmark = 51820;
setsockopt(sock_fd, SOL_SOCKET, SO_MARK, &fwmark, sizeof(fwmark));
Все исходящие UDP-датаграммы этого сокета получают числовую метку 51820. Для управления трафиком настраиваются правила RPDB:
# Правила для немаркированного и маркированного трафика
ip rule add not fwmark 51820 table 51820
ip rule add table main suppress_prefixlength 0
Процесс обработки выполняется следующим образом:
- Трафик приложений: Браузер отправляет пакет к
203.0.113.50. Сокет браузера не имеет метки, поэтомуskb->markравен0. Ядро проверяет правилоnot fwmark 51820. Поскольку0 != 51820, условие выполняется. Пакет направляется в таблицу 51820 и поступает в интерфейсwg0. - Транспортный трафик WireGuard: WireGuard шифрует полезную нагрузку и отправляет внешний UDP-пакет через маркированный сокет (
skb->mark = 51820). Ядро проверяет правилоnot fwmark 51820. Так как метка совпадает с51820, инверсное правило не срабатывает. Ядро переходит к следующему правилу в RPDB — таблицеmain(254). В таблицеmainфизический шлюз черезeth0илиwlan0отправляет пакет напрямую локальному маршрутизатору.
Такой механизм гарантирует, что зашифрованные пакеты туннеля покидают систему через физический интерфейс и не попадают обратно в wg0.
Фильтрация обратного пути и параметр rp_filter
Даже при корректной исходящей маршрутизации туннель может давать сбои, если механизмы безопасности входящего трафика блокируют ответы. Главной причиной является фильтрация обратного пути (Reverse Path Filtering, RPF), описанная в RFC 3704 и контролируемая параметром rp_filter.
RPF защищает систему от подмены IP-адресов. При получении пакета ядро проверяет, использовался ли бы тот же интерфейс для отправки ответа на исходный адрес.
В Linux предусмотрено три режима RPF:
0(Отключено): проверка адреса источника не выполняется.1(Строгий режим): входящий пакет должен поступить строго на тот интерфейс, который выбран таблицей маршрутизации для обратного пути.2(Мягкий режим): адрес источника должен иметь корректный маршрут в любой таблице маршрутизации, независимо от интерфейса.
В строгом режиме (rp_filter = 1) асимметрия политик маршрутизации приводит к сбросу пакетов. Когда ответный пакет поступает из интернета на физический интерфейс eth0, ядро выполняет обратную проверку маршрута. Поскольку этот поиск не содержит метки fwmark, RPDB направляет запрос в таблицу 51820, которая указывает на интерфейс wg0. Несоответствие интерфейсов расценивается ядром как попытка спуфинга, и пакет отбрасывается на канальном уровне.
Для предотвращения потери пакетов в конфигурациях WireGuard включается мягкий режим RPF:
sysctl -w net.ipv4.conf.all.rp_filter=2
sysctl -w net.ipv4.conf.eth0.rp_filter=2
sysctl -w net.ipv4.conf.wg0.rp_filter=2
Директива suppress_prefixlength 0 в правиле RPDB гарантирует, что маршрут по умолчанию (/0) туннельной таблицы не перекрывает более специфичные локальные подсети в таблице main.
Мобильные песочницы и десктоп: Android VpnService и сетевые пространства имен
Использование параметра SO_MARK требует административных привилегий CAP_NET_ADMIN. На рабочих станциях и серверах Linux права суперпользователя позволяют напрямую манипулировать сокетами и таблицами RPDB. Однако в мобильных операционных системах строгая изоляция приложений не допускает прямого изменения таблиц ядра.
В Android клиентские приложения не могут вызывать setsockopt(SO_MARK). Для решения этой задачи платформа предоставляет интерфейс android.net.VpnService. При создании транспортного UDP-сокета приложение передает файловый дескриптор системе через вызов VpnService.protect(int socketFd). Системный демон Android привязывает этот сокет напрямую к физической сети, исключая попадание в виртуальный интерфейс tun.
Linux Desktop: Сокет -> SO_MARK (51820) -> Правило RPDB (not fwmark) -> Таблица main
Android Mobile: Сокет -> VpnService.protect() -> Прямая привязка к физической сети
Network Namespaces: Интерфейс eth0 в netns0 <-> wg0 в init_net (полная изоляция без меток)
В серверных решениях высокой надежности сетевые пространства имен (ip netns) предлагают альтернативный способ предотвращения петель. Перенос физического интерфейса eth0 в отдельный namespace при сохранении wg0 в основном окружении физически исключает рекурсию маршрутизации без необходимости маркировки пакетов.
Архитектура политик RiceGuard и защита от утечек DNS
В реальных условиях клиентские приложения VPN должны стабильно функционировать при переключении между точками Wi-Fi, смене параметров DHCP и работе локальных системных резолверов DNS, таких как systemd-resolved на 127.0.0.53.
RiceGuard применяет многоуровневый механизм маршрутизации по правилам:
- Приоритетные цепочки RPDB: Изоляция локальных адресов, подсетей LAN и многоадресного трафика до перенаправления общего потока в виртуальный туннель.
- Интеграция с брандмауэром: Синхронизация правил ядра с цепочками
nftables. При падении туннеля пакеты без разрешенной меткиfwmarkне могут покинуть физический интерфейс в незашифрованном виде. - Защита системных запросов DNS: Запросы к локальным службам разрешения имен изолируются и направляются исключительно через шифрованный канал.
Согласованная работа меток пакетов, базы данных правил и механизмов фильтрации обеспечивает стабильную передачу данных и надежную защиту сетевого соединения.



