Split tunneling: как разделить VPN-трафик и обычный интернет без путаницы в маршрутах

Что решает split tunneling​


Полный туннель направляет весь трафик машины через VPN-интерфейс. Это безопасно, но создаёт проблемы: локальные сервисы становятся недоступны, скорость падает из-за лишнего шифрования для трафика, который в защите не нуждается, а некоторые приложения (банковские клиенты, корпоративные агенты) перестают работать из-за смены IP.

Split tunneling решает это выборочно: часть трафика идёт через туннель, часть — напрямую через обычный шлюз. Разделение может строиться по адресу назначения, по приложению или по процессу.

AllowedIPs как основа разделения в WireGuard​


В WireGuard маршрутизация определяется полем AllowedIPs в конфигурации пира. При отправке пакета интерфейс смотрит на IP назначения и ищет совпадение в списках AllowedIPs всех пиров. Если совпадение найдено — пакет шифруется и уходит соответствующему пиру. Если нет — пакет отбрасывается.

Полный туннель задаётся записью AllowedIPs = 0.0.0.0/0 — это wildcard, который перехватывает все IPv4-адреса. Split tunneling достигается сужением этого списка до конкретных подсетей:

INI:
[Interface]
PrivateKey = gI6EdUSYvn8ugXOt8QQD6Yc+JyiZxIhp3GInSWRfWGE=
ListenPort = 21841

[Peer]
PublicKey = HIgo9xNzJMWLKASShiTqIybxZ0U3wGLiUeJ1PKf8ykw=
Endpoint = 192.95.5.69:51820
AllowedIPs = 10.0.0.0/8, 172.16.0.0/12

В этой конфигурации через туннель уйдёт только трафик к подсетям 10.0.0.0/8 и 172.16.0.0/12. Всё остальное пойдёт через обычный маршрут по умолчанию. Это простейший вариант split tunneling — разделение по адресу назначения.

Можно указать и отдельные хосты:

INI:
AllowedIPs = 10.192.122.3/32, 10.192.124.1/24

Здесь туннель используется только для одного конкретного хоста и одной подсети. Такой подход удобен, когда VPN нужен для доступа к внутренней сети, а весь остальной интернет должен работать напрямую.

Маршрутные таблицы: классические подходы​


Когда split tunneling нужен не на уровне конфигурации WireGuard, а на уровне системы (например, туннель уже поднят с 0.0.0.0/0, но часть трафика нужно исключить), используются маршрутные таблицы Linux.

Замена маршрута по умолчанию с исключением для endpoint​


Самый прямолинейный способ — удалить default route, добавить его через туннель и явно прописать маршрут до VPN-сервера через физический интерфейс:

Bash:
ip route del default
ip route add default dev wg0
ip route add 163.172.161.0/32 via 192.168.1.1 dev eth0

Проблема: DHCP-демоны и сетевые менеджеры периодически перезаписывают таблицу маршрутизации, и исключение для endpoint теряется.

Два маршрута /1 вместо default​


Более устойчивый вариант — не трогать default route, а перекрыть его двумя более специфичными маршрутами, которые в сумме покрывают всё адресное пространство:

Bash:
ip route add 0.0.0.0/1 dev wg0
ip route add 128.0.0.0/1 dev wg0
ip route add 163.172.161.0/32 via 192.168.1.1 dev eth0

Маршруты /1 имеют более длинный префикс, чем /0, поэтому ядро выбирает их раньше. Default route остаётся нетронутым, и DHCP не конфликтует. Недостаток тот же: при переподключении eth0 явный маршрут до endpoint может исчезнуть.

Rule-based routing с несколькими таблицами​


Для сложных сценариев создаются отдельные таблицы маршрутизации и правила выбора между ними:

Bash:
ip rule add to 163.172.161.0 lookup main pref 30
ip rule add to all lookup 80 pref 40
ip route add default dev wg0 table 80

Здесь таблица 80 содержит маршрут через туннель, а правило с приоритетом 30 гарантирует, что трафик к endpoint VPN всегда идёт через основную таблицу. Минус: правила не очищаются автоматически при удалении интерфейса, и при изменении endpoint нужно обновлять их вручную.

Техника fwmark (используется в wg-quick)​


Инструмент wg-quick применяет более элегантный подход. На UDP-сокет WireGuard устанавливается fwmark — метка, которая позволяет ядру отличать пакеты туннеля от обычных:

Bash:
wg set wg0 fwmark 1234
ip route add default dev wg0 table 2468
ip rule add not fwmark 1234 table 2468
ip rule add table main suppress_prefixlength 0

Логика:

  1. Все пакеты, выходящие из UDP-сокета WireGuard, получают метку 1234.
  2. Правило not fwmark 1234 table 2468 направляет все пакеты без метки в таблицу 2468, где default route ведёт через wg0.
  3. Пакеты с меткой (то есть зашифрованные данные самого туннеля) не попадают в таблицу 2468 и идут через основную таблицу — так избегается петля маршрутизации.
  4. suppress_prefixlength 0 позволяет пакетам без метки использовать маршруты из основной таблицы, если они не являются default route (например, локальная подсеть).

Эта схема устойчива к смене endpoint, потому что не зависит от конкретного IP-адреса сервера.

Network namespaces: разделение по процессам​


Когда нужно направить через туннель не определённые адреса, а конкретные приложения, используются сетевые пространства имён. WireGuard запоминает namespace, в котором был создан, и его UDP-сокет всегда остаётся в этом namespace. Это позволяет создать интерфейс в одном namespace, переместить в другой, и при этом зашифрованные пакеты будут уходить через физический интерфейс исходного namespace.

Изоляция контейнера​


Типичный сценарий — Docker-контейнер, у которого единственный сетевой интерфейс это wg0:

Bash:
ip netns add container
ip link add wg0 type wireguard
ip link set wg0 netns container
ip -n container addr add 192.168.4.33/32 dev wg0
ip netns exec container wg setconf wg0 /etc/wireguard/wg0.conf
ip -n container link set wg0 up
ip -n container route add default dev wg0

После этого контейнер не имеет прямого доступа к сети — весь его трафик проходит через туннель. Это не split tunneling в чистом виде, а полная изоляция, но механизм тот же.

Обратная задача: весь трафик через туннель, кроме выбранных процессов​


Более интересный для split tunneling сценарий: физические интерфейсы (eth0, wlan0) переносятся в отдельный namespace, а в основном остаётся только wg0. Все обычные процессы видят только туннель. Но при необходимости можно запустить конкретное приложение в «физическом» namespace:

Bash:
ip netns add physical
ip link set eth0 netns physical
iw phy phy0 set netns name physical

ip -n physical link add wg0 type wireguard
ip -n physical link set wg0 netns 1

ip netns exec physical dhcpcd wlan0
wg setconf wg0 /etc/wireguard/wg0.conf
ip addr add 10.2.4.5/32 dev wg0
ip link set wg0 up
ip route add default dev wg0

Теперь для запуска браузера напрямую, минуя туннель:

Bash:
sudo -E ip netns exec physical sudo -E -u \#$(id -u) -g \#$(id -g) chromium

Это удобно, например, для авторизации в captive portal кофейни, пока весь остальной трафик защищён.

Диагностика и проверка​


После настройки split tunneling важно убедиться, что трафик действительно распределяется так, как задумано.

Проверка таблицы маршрутизации​


Bash:
ip route show
ip route show table 2468
ip rule list

Убедитесь, что маршруты через wg0 покрывают только нужные подсети, а default route (если он нужен для прямого трафика) указывает на физический интерфейс.

Трассировка конкретного адреса​


Bash:
ip route get 8.8.8.8
ip route get 10.0.0.1

Первая команда покажет, через какой интерфейс уйдёт трафик к внешнему адресу. Вторая — к внутреннему. Если split tunneling настроен правильно, результаты будут разными.

Проверка fwmark​


Bash:
wg show wg0 fwmark

Если fwmark установлен, убедитесь, что соответствующее правило существует:

Bash:
ip rule list | grep 1234

Мониторинг в реальном времени​


Bash:
tcpdump -i wg0 -n host 10.0.0.1
tcpdump -i eth0 -n host 8.8.8.8

Если пакеты к 10.0.0.1 видны на wg0, а к 8.8.8.8 — на eth0, разделение работает.

Типичные ошибки​


Петля маршрутизации. Если маршрут до endpoint VPN тоже ведёт через wg0, туннель не поднимется. Всегда проверяйте, что для IP сервера существует явный маршрут через физический интерфейс или что fwmark-правило корректно исключает пакеты туннеля.

Конфликт с NetworkManager или systemd-networkd. Эти сервисы периодически перезаписывают маршруты. Если используете ручные правила, либо отключите управление интерфейсом wg0 со стороны сетевого менеджера, либо используйте fwmark-подход, который не зависит от default route.

Неверный порядок правил. В ip rule приоритет определяется числом: меньшее значение обрабатывается раньше. Если правило для туннеля имеет меньший приоритет, чем правило исключения, трафик уйдёт не туда.

Забытый маршрут при смене сети. При переключении с Wi-Fi на мобильные данные или между точками доступа IP шлюза меняется. Явные маршруты вида via 192.168.1.1 перестают работать. Fwmark-подход и namespace-подход лишены этого недостатка.

Выбор подхода​


СценарийПодход
Доступ к внутренней сети через VPN, интернет напрямуюAllowedIPs с конкретными подсетями
Весь трафик через VPN, исключение для endpointfwmark + альтернативная таблица
Конкретное приложение мимо туннеляNetwork namespaces
Контейнер с единственным VPN-интерфейсомNamespace + перемещение wg0
Быстрое переключение без перезапускаRule-based routing с несколькими таблицами

Источники​


 
Назад
Верх Низ