Раздельное туннелирование в Linux нужно, когда через туннель должны идти не все пакеты подряд, а только часть трафика. Классический пример простой: ноутбук должен ходить в корпоративные подсети через VPN, а YouTube, обновления системы и обычный веб пусть идут напрямую через домашний интернет. На словах схема звучит просто. На практике большинство проблем рождается не в самом туннеле, а в маршрутах, DNS и автоматике сетевого менеджера.
Главная мысль такая. В Linux раздельное туннелирование почти никогда не сводится к одной галочке в клиенте. Ядро выбирает маршрут по таблицам и правилам, а VPN-клиент только помогает добавить нужные записи. Поэтому устойчивую настройку строят вокруг обычных инструментов Linux: маршрутов, policy routing, меток пакетов и аккуратной работы с DNS.
Материал описывает обычное сетевое администрирование. Используйте туннели только в законных и рабочих сценариях, соблюдайте местные нормы, в том числе требования законодательства России. Текст не предназначен для обхода блокировок, сокрытия запрещенной деятельности или обхода ограничений доступа.
Что такое раздельное туннелирование в Linux простыми словами
Есть два базовых режима. Полный туннель меняет маршрут по умолчанию и гонит почти весь трафик через VPN. Раздельный режим оставляет обычный маршрут для интернета, а в туннель отправляет только нужные сети, нужные приложения или трафик конкретного пользователя. В Linux для такого выбора обычно используют три подхода.
| Подход | Что выбираем | Когда удобно | Где чаще ломается |
|---|---|---|---|
| Маршруты по подсетям | Сети и диапазоны адресов | Корпоративные сегменты, доступ к внутренним сервисам | DNS уходит не туда, пересекаются частные подсети |
| Policy routing | Пользователь, порт, протокол, метка пакета | Нужно пустить через туннель только браузер, контейнер или отдельный сервис | Сложнее поддерживать, легко забыть про приоритет правил |
| Отдельное сетевое пространство | Целый процесс или группу процессов | Когда нужна жесткая изоляция трафика приложения | Больше возни с запуском приложений и DNS |
На чем держится вся схема
В ядре Linux уже есть все, что нужно. Обычная таблица маршрутов main хранит привычные пути. Поверх нее работает база правил маршрутизации. Команда ip-rule как раз описывает логику, где правило может выбирать маршрут не только по адресу назначения, но и по источнику, метке пакета, UID, протоколу и даже порту. Для раздельного туннелирования это ключевой механизм.
Из-за этого полезно сразу привыкнуть к трем командам, которые быстро показывают картину:
ip rule show
ip route show table all
ip route get 10.10.10.20
Первая покажет, в каком порядке ядро проверяет правила. Вторая даст все таблицы маршрутов. Третья ответит на самый важный вопрос: каким путем конкретный адрес пойдет прямо сейчас.
Самый простой и самый надежный вариант: гнать в туннель только нужные подсети
Если задача звучит как «в корпоративную сеть через VPN, все остальное напрямую», не начинайте с меток пакетов и хитрых фильтров. В девяти случаях из десяти хватает обычных маршрутов по подсетям. Такой вариант легче читать, легче сопровождать и намного проще отлаживать.
Пример с уже поднятым интерфейсом wg0 или tun0:
ip route add 10.10.0.0/16 dev wg0
ip route add 172.20.50.0/24 dev wg0
Теперь адреса из этих двух сетей уйдут в туннель, а остальной трафик останется на обычном маршруте по умолчанию. Проверка:
ip route get 10.10.5.10
ip route get 1.1.1.1
Первый запрос должен показать выход через wg0, второй через обычный интернет-интерфейс.
У такого подхода есть сильная сторона. Маршруты прозрачны. Но есть и слабое место. Если корпоративная сеть использует те же частные адреса, что домашний роутер, например популярный диапазон 192.168.0.0/24, начнется путаница. В таком случае помогает либо более точная адресация, либо отдельная таблица маршрутов, либо полная изоляция в отдельном сетевом пространстве.
WireGuard: два сценария, которые реально работают
С WireGuard есть важный нюанс. Утилита wg-quick умеет сама добавлять маршруты на основе AllowedIPs. Поэтому раздельное туннелирование часто настраивают прямо в конфигурации, без отдельной ручной магии.
Сценарий 1. Только корпоративные сети через туннель
Самый чистый вариант выглядит так:
[Interface]
Address = 10.200.100.8/32
PrivateKey = ...
[Peer]
PublicKey = ...
Endpoint = vpn.example.com:51820
AllowedIPs = 10.10.0.0/16, 172.20.50.0/24
PersistentKeepalive = 25
В такой схеме WireGuard добавит маршруты только к указанным подсетям. Интернет-маршрут по умолчанию останется обычным. Для офисного доступа этого часто достаточно.
Сценарий 2. Через туннель идет только конкретное приложение или пользователь
Здесь уже нужен не просто список подсетей, а отдельная таблица маршрутов. У wg-quick для этого есть полезный режим Table = off, который отключает автоматическое создание маршрутов. Дальше вы сами решаете, какой трафик отправлять в туннель.
[Interface]
Address = 10.200.100.8/32
PrivateKey = ...
Table = off
[Peer]
PublicKey = ...
Endpoint = vpn.example.com:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
После подъема интерфейса создаем отдельную таблицу, например 100:
ip route add default dev wg0 table 100
ip rule add uidrange 1001-1001 lookup 100 priority 1000
Теперь весь трафик пользователя с UID 1001 пойдет через wg0, а остальные процессы останутся на обычном интернете. Для такого режима удобно создать отдельного системного пользователя и запускать нужную программу от его имени.
Технически схема выглядит красиво, но здесь легко ошибиться. Помощники браузера, обновляторы, медиапроцессы и фоновые службы могут работать от другого UID или открывать соединения вне ожидаемой логики. Когда нужна не «примерно правильная» маршрутизация, а жесткая, надежнее переходить к меткам пакетов или к отдельному сетевому пространству.
Раздельное туннелирование через метки пакетов
Маркировка нужна, когда выбирать трафик по UID уже неудобно, а по адресу назначения слишком грубо. Например, один и тот же сервис общается и с внутренними адресами, и с внешними. Тогда пакет сначала помечают, а потом правило ip rule смотрит на метку и отправляет трафик в нужную таблицу.
С nftables базовая идея такая:
nft add table inet mangle
nft 'add chain inet mangle output { type route hook output priority mangle; }'
nft add rule inet mangle output meta skuid 1001 meta mark set 0x64
ip route add default dev wg0 table 100
ip rule add fwmark 0x64 lookup 100 priority 1000
Смысл простой. Пакеты пользователя 1001 получают метку 0x64, после чего ядро ищет для них маршрут в таблице 100. Такой подход гибче, чем чистый uidrange, потому что вы можете метить не только по пользователю, но и по протоколу, порту, группе процессов и другим признакам.
Но и цена выше. Как только в схеме появляется nftables, сложность резко растет. Ошиблись приоритетом цепочки, забыли сохранить правила после перезагрузки, не учли IPv6, и половина трафика внезапно уходит не туда.
OpenVPN: что делать, если сервер пытается затянуть в полный туннель
У OpenVPN проблема часто выглядит так. Вы хотите гонять через туннель только внутренние сети, а сервер пушит клиенту redirect-gateway и превращает подключение в полный туннель. На стороне клиента для раздельного режима обычно используют два приема из OpenVPN.
Первый прием жёсткий. Запретить автоматическую установку маршрутов:
client
dev tun
proto udp
remote vpn.example.com 1194
route-nopull
route 10.10.0.0 255.255.0.0
route 172.20.50.0 255.255.255.0
Второй прием мягче. Игнорировать только попытку сделать полный туннель:
client
dev tun
proto udp
remote vpn.example.com 1194
pull-filter ignore "redirect-gateway"
route 10.10.0.0 255.255.0.0
route 172.20.50.0 255.255.255.0
Первый вариант строже и предсказуемее. Второй удобнее, когда сервер еще присылает полезные параметры, а вы хотите выкинуть только навязанный маршрут по умолчанию. В обоих случаях нужно помнить, что OpenVPN может обновлять DNS и свойства интерфейса по своим правилам, поэтому после подключения всегда проверяйте не только маршруты, но и резолвер.
DNS ломает раздельное туннелирование чаще, чем сами маршруты
Сценарий типичный. Вы аккуратно отправили в VPN только сеть 10.10.0.0/16, а внутренний адрес git.corp.example все равно не открывается. Причина банальна: запрос имени ушел на публичный DNS, который о внутренней зоне ничего не знает. Или хуже, корпоративное имя уходит наружу и светится там, где светиться не должно.
Поэтому раздельное туннелирование без раздельного DNS почти всегда недоделано. Если у вас systemd-resolved, на интерфейс туннеля обычно вешают отдельный DNS и routing-only домен:
resolvectl dns wg0 10.10.0.53
resolvectl domain wg0 '~corp.example'
Такой режим говорит системе: запросы к зоне corp.example отправляй к DNS на интерфейсе wg0, а остальной DNS оставь обычному каналу. В корпоративной среде это обычно правильнее, чем переписывать глобальный /etc/resolv.conf под VPN.
Если используется NetworkManager, смотрите не только маршруты, но и его DNS-поведение. Иначе получите странную смесь, где трафик к внутренней сети идет через VPN, а DNS остался у домашнего роутера.
Где раздельное туннелирование реально полезно
Полезных сценариев много, и почти все они скучнее, чем любят рассказывать в интернете. Корпоративный доступ к GitLab и Jira при обычном домашнем интернете. Администрирование нескольких внутренних подсетей без перетаскивания всего внешнего трафика через офис. Подключение к частным базам данных и файловым ресурсам, когда большой внешний трафик через туннель только мешает. Маршрутизация трафика одного сервиса через отдельный защищенный канал без перестройки всей машины.
Во всех этих случаях раздельная схема не делает сеть «невидимой» и не превращает Linux в анонимный черный ящик. Система просто выбирает разные пути для разных пакетов. Не больше.
Где раздельное туннелирование лучше не включать
Есть и обратная сторона. Для защищенного корпоративного ноутбука split tunnel часто хуже полного туннеля. Пока часть трафика идет напрямую в интернет, машина одновременно остается в локальной сети кафе или гостиницы и имеет маршрут к внутренним ресурсам компании. С точки зрения безопасности такая связка нравится далеко не всем ИБ-командам.
Плохая идея включать раздельный режим на рабочих станциях администраторов продакшена, на устройствах с доступом к чувствительным данным, при работе через публичный Wi-Fi, а также там, где важен полный контроль журналирования, фильтрации и веб-инспекции на стороне корпоративного шлюза. В таких сценариях полный туннель часто честнее и безопаснее.
Три ошибки, из-за которых схема ведет себя «как будто случайно»
- Смешали IPv4 и IPv6. Настроили split tunnel только для IPv4, а IPv6 остался идти напрямую.
- Забыли про DNS. Маршруты правильные, а имена внутренних узлов резолвятся через внешний сервер.
- Не проверили приоритет правил. В Linux меньший номер приоритета важнее, и одно неудачное правило легко перекрывает другое.
Четвертая ошибка встречается не реже, просто про нее вспоминают поздно. После переподключения Wi-Fi, DHCP-обновления или перезапуска NetworkManager автоматические маршруты меняются, а ручные правила остаются. В результате вчерашняя схема внезапно работает по-другому. Если конфигурация нужна надолго, не ограничивайтесь ручными командами в терминале. Закрепляйте настройки в systemd-networkd, NetworkManager, wg-quick hooks или в собственных unit-файлах.
Практическая последовательность, с которой лучше начинать
Сначала определите, что именно надо отправить в туннель: подсети, домены, пользователя, сервис или целый контейнер. Потом выберите самый простой механизм. Если хватает маршрутов по подсетям, не переходите сразу к nftables. Если нужен только один процесс, подумайте не только про uidrange, но и про отдельного системного пользователя. Если схема становится слишком хитрой, честно оцените, не проще ли выделить приложению отдельное сетевое пространство.
После настройки проверьте четыре вещи. Куда идет маршрут к внутреннему адресу. Куда идет маршрут к публичному адресу. Как резолвятся внутренние имена. Не утекает ли IPv6 мимо вашей схемы. Пока все четыре пункта не проверены, считать split tunnel готовым рано.
Что в итоге
Раздельное туннелирование в Linux не является «функцией VPN-клиента» в узком смысле. Это обычная маршрутизация, только чуть умнее обычной. Самая здоровая стратегия такая: сначала пробовать маршруты по подсетям, потом policy routing по UID или меткам, и только потом переходить к более тяжелым схемам. WireGuard удобно решает простые случаи через AllowedIPs. OpenVPN требует внимательнее смотреть на push-опции сервера. DNS нужно проектировать вместе с маршрутами, а не вспоминать про него после первых жалоб.
Если нужен короткий практический вывод, он простой. Для большинства рабочих Linux-машин хорошее раздельное туннелирование строится не на хитрости, а на минимализме. Чем меньше правил и чем прозрачнее маршруты, тем меньше шанс, что через неделю вы будете ловить «странное поведение сети» вместо нормальной работы.
FAQ
Можно ли сделать split tunnel только для одного браузера?
Да. Обычно через отдельного пользователя, метки пакетов или отдельное сетевое пространство. Самый надежный вариант для строгой изоляции обычно не порт, а отдельный namespace.
Достаточно ли просто добавить пару маршрутов через wg0?
Если нужно пустить через туннель только несколько подсетей, часто да. Но после этого все равно проверьте DNS и IPv6.
Почему внутренний хост по имени не открывается, а по IP открывается?
Почти наверняка DNS-запрос уходит не на внутренний резолвер. Маршрут к подсети настроен, а раздельный DNS нет.
Можно ли считать раздельное туннелирование более безопасным, чем полный туннель?
Не всегда. Для производительности и удобства такой режим часто лучше. Для корпоративной безопасности и работы через публичные сети нередко наоборот хуже.
Что проще для старта, WireGuard или OpenVPN?
Для чистого сценария «эти подсети через туннель, остальное напрямую» WireGuard обычно проще. В OpenVPN чаще приходится разруливать push-опции сервера и поведение клиента.