Маршрутизация и сетевые пространства имён
Интеграция с маршрутизацией и сетевыми пространствами имён
Как и все сетевые интерфейсы Linux, WireGuard интегрируется в инфраструктуру сетевых пространств имён (network namespaces). Это означает, что администратор может иметь несколько полностью разных сетевых подсистем и выбирать, какие интерфейсы находятся в каждом из них.
Что такое сетевые пространства имён? Это механизм ядра Linux, который позволяет изолировать сетевые стеки. Каждое пространство имён имеет свои собственные сетевые интерфейсы, таблицы маршрутизации, правила файервола и сокеты. Это основа для контейнеризации (Docker, LXC и другие).
WireGuard делает кое-что довольно интересное. Когда интерфейс WireGuard создаётся (с помощью ip link add wg0 type wireguard), он запоминает пространство имён, в котором был создан: «Я был создан в пространстве имён A». Позже WireGuard может быть перемещён в новые пространства имён («Я перемещаюсь в пространство имён B»), но он всё равно будет помнить, что произошёл из пространства имён A.
Почему это важно? WireGuard использует UDP-сокет для фактической отправки и получения зашифрованных пакетов. Этот сокет всегда живёт в пространстве имён A — исходном пространстве имён рождения. Это обеспечивает очень интересные возможности. А именно, вы можете создать интерфейс WireGuard в одном пространстве имён (A), переместить его в другое (B) и отправлять незашифрованные пакеты из пространства имён B, которые будут отправляться зашифрованными через UDP-сокет в пространстве имён A.
Это открывает очень интересные возможности.
Обычная контейнеризация
Наиболее очевидное использование этого — предоставить контейнерам (например, Docker-контейнерам) интерфейс WireGuard в качестве единственного интерфейса.
Пример: Внутри контейнера видно только loopback-интерфейс и интерфейс WireGuard:
container # ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
17: wg0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1423 qdisc noqueue state UNKNOWN group default qlen 1
link/none
inet 192.168.4.33/32 scope global wg0
valid_lft forever preferred_lft forever
Что это значит? Единственный способ доступа к сети — через wg0, интерфейс WireGuard. Весь трафик из контейнера автоматически шифруется и отправляется через VPN. Контейнер даже не знает о существовании физических сетевых интерфейсов хоста.
Настройка контейнера с WireGuard
Шаг 1: Создание сетевого пространства имён
Сначала создаём сетевое пространство имён с именем «container»:
# ip netns add container
Шаг 2: Создание интерфейса WireGuard
Создаём интерфейс WireGuard в исходном пространстве имён (init):
# ip link add wg0 type wireguard
Шаг 3: Перемещение интерфейса
Перемещаем интерфейс в новое пространство имён:
# ip link set wg0 netns container
Шаг 4: Настройка интерфейса
Теперь настраиваем wg0 как обычно, но указываем его новое пространство имён:
# 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
Результат: Теперь единственный способ доступа к сетевым ресурсам для «container» — через интерфейс WireGuard.
Для пользователей Docker: Вы можете указать PID Docker-процесса вместо имени сетевого пространства имён, чтобы использовать сетевое пространство имён, которое Docker уже создал для своего контейнера:
# ip link set wg0 netns 879
Маршрутизация всего трафика
Менее очевидное, но чрезвычайно мощное использование — применение этой характеристики WireGuard для перенаправления всего вашего обычного интернет-трафика через WireGuard.
Проблема: Как заставить весь трафик идти через VPN, но при этом позволить самому VPN-соединению использовать обычный интернет?
Классические решения
Классические решения основаны на различных конфигурациях таблиц маршрутизации. Для всех них нам нужно установить явный маршрут для фактической конечной точки WireGuard.
Пример: Предположим, конечная точка WireGuard — demo.wireguard.com, которая на момент написания резолвится в 163.172.161.0. Предположим также, что мы обычно подключаемся к интернету через eth0 со шлюзом 192.168.1.1.
Замена маршрута по умолчанию
Самая простая техника — просто заменить маршрут по умолчанию, но добавить явное правило для конечной точки WireGuard:
# 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-демоны и другие программы любят отменять то, что мы только что сделали.
Переопределение маршрута по умолчанию
Вместо замены маршрута по умолчанию мы можем переопределить его двумя более специфичными правилами, которые в сумме дают маршрут по умолчанию, но совпадают раньше:
# 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
Объяснение: 0.0.0.0/1 + 128.0.0.0/1 = 0.0.0.0/0 (весь IPv4-диапазон). Эти маршруты имеют более высокий приоритет, чем маршрут по умолчанию, поэтому они используются для всего трафика, кроме явно исключённого.
Недостатки: Когда eth0 поднимается и опускается, явный маршрут для demo.wireguard.com забывается.
Маршрутизация на основе правил
Некоторые предпочитают использовать маршрутизацию на основе правил и несколько таблиц маршрутизации:
# 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
Объяснение: Правила определяют, какую таблицу маршрутизации использовать для каждого пакета. Для IP-адреса конечной точки используется основная таблица, для всего остального — таблица 80.
Недостатки: Нужно добавлять явные правила для конечных точек, нет очистки при удалении интерфейса, более сложные правила маршрутизации нужно дублировать.
Улучшенная маршрутизация на основе правил
Предыдущее решение требует знания явного IP-адреса конечной точки, который должен быть исключён из туннеля. Но конечные точки WireGuard могут роумить, что означает, что это правило может устареть.
Решение: Устанавливаем fwmark на все пакеты, выходящие из UDP-сокета WireGuard, которые затем будут исключены из туннеля:
# 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
Объяснение:
- Устанавливаем
fwmarkна интерфейсе - Добавляем маршрут по умолчанию в альтернативную таблицу маршрутизации
- Указываем, что пакеты без
fwmarkдолжны использовать эту альтернативную таблицу - Добавляем удобство для доступа к локальной сети
Это техника, используемая инструментом wg-quick(8).
Новое решение с пространствами имён
Оказывается, мы можем маршрутизировать весь интернет-трафик через WireGuard, используя сетевые пространства имён, а не классические хаки с таблицами маршрутизации.
Основная идея: Перемещаем интерфейсы, подключающиеся к интернету (например, eth0 или wlan0), в пространство имён «physical», а интерфейс WireGuard делаем единственным интерфейсом в пространстве имён «init».
Пошаговая настройка
Шаг 1: Создание физического пространства имён
# ip netns add physical
Шаг 2: Перемещение физических интерфейсов
Перемещаем eth0 и wlan0 в пространство имён «physical»:
# ip link set eth0 netns physical
# iw phy phy0 set netns name physical
iw путём указания физического устройства phy0.Теперь эти интерфейсы находятся в пространстве имён «physical», а в пространстве имён «init» интерфейсов нет:
# ip -n physical link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN mode DEFAULT group default qlen 1000
link/ether ab:cd:ef:g1:23:45 brd ff:ff:ff:ff:ff:ff
3: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DORMANT group default qlen 1000
link/ether 01:23:45:67:89:ab brd ff:ff:ff:ff:ff:ff
# ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
Шаг 3: Создание интерфейса WireGuard в физическом пространстве
# ip -n physical link add wg0 type wireguard
Пространство имён рождения wg0 теперь — «physical», что означает, что UDP-сокеты с зашифрованными данными будут привязаны к устройствам eth0 и wlan0.
Шаг 4: Перемещение интерфейса в init
Перемещаем wg0 в пространство имён «init». Он всё равно будет помнить своё место рождения для сокетов:
# ip -n physical link set wg0 netns 1
Теперь в пространстве имён «init» есть устройство wg0:
# ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
17: wg0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1423 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1
link/none
Шаг 5: Настройка физических устройств
Настраиваем физические устройства с помощью обычных инструментов, но запускаем их внутри пространства имён «physical»:
# ip netns exec physical dhcpcd wlan0
# ip netns exec physical wpa_supplicant -iwlan0 -c/etc/wpa_supplicant/wpa_supplicant.conf
# ip -n physical addr add 192.168.12.52/24 dev eth0
Шаг 6: Настройка WireGuard
Настраиваем интерфейс wg0 как обычно и устанавливаем его как маршрут по умолчанию:
# 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
Результат: Все обычные процессы в системе маршрутизируют свои пакеты через пространство имён «init», которое содержит только интерфейс wg0 и его маршруты. Однако wg0 имеет UDP-сокет, живущий в пространстве имён «physical», что означает, что он будет отправлять трафик через eth0 или wlan0. Обычные процессы даже не знают о существовании eth0 или wlan0, за исключением dhcpcd и wpa_supplicant, которые были запущены внутри пространства имён «physical».
Выполнение команд в физическом пространстве
Иногда вам может понадобиться открыть веб-страницу или быстро сделать что-то, используя пространство имён «physical». Например, вы можете планировать маршрутизировать весь трафик через WireGuard, но кофейня, в которой вы сидите, требует аутентификации через веб-сайт перед предоставлением реального интернет-соединения.
Выполнение процесса в физическом пространстве:
$ sudo -E ip netns exec physical sudo -E -u \#$(id -u) -g \#$(id -g) chromium
Создание удобной функции для .bashrc:
physexec() { sudo -E ip netns exec physical sudo -E -u \#$(id -u) -g \#$(id -g) "$@"; }
Использование:
$ physexec chromium # Открыть Chromium в физическом пространстве для аутентификации
После входа в сеть кофейни запускайте браузер как обычно:
$ chromium # Весь трафик защищён WireGuard
Пример скрипта
Следующий пример скрипта можно сохранить как /usr/local/bin/wgphys и использовать для команд wgphys up, wgphys down и wgphys exec:
#!/bin/bash
set -ex
[[ $UID != 0 ]] && exec sudo -E "$(readlink -f "$0")" "$@"
up() {
killall wpa_supplicant dhcpcd || true
ip netns add physical
ip -n physical link add wgvpn0 type wireguard
ip -n physical link set wgvpn0 netns 1
wg setconf wgvpn0 /etc/wireguard/wgvpn0.conf
ip addr add 192.168.4.33/32 dev wgvpn0
ip link set eth0 down
ip link set wlan0 down
ip link set eth0 netns physical
iw phy phy0 set netns name physical
ip netns exec physical dhcpcd -b eth0
ip netns exec physical dhcpcd -b wlan0
ip netns exec physical wpa_supplicant -B -c/etc/wpa_supplicant/wpa_supplicant-wlan0.conf -iwlan0
ip link set wgvpn0 up
ip route add default dev wgvpn0
}
down() {
killall wpa_supplicant dhcpcd || true
ip -n physical link set eth0 down
ip -n physical link set wlan0 down
ip -n physical link set eth0 netns 1
ip netns exec physical iw phy phy0 set netns 1
ip link del wgvpn0
ip netns del physical
dhcpcd -b eth0
dhcpcd -b wlan0
wpa_supplicant -B -c/etc/wpa_supplicant/wpa_supplicant-wlan0.conf -iwlan0
}
execi() {
exec ip netns exec physical sudo -E -u \#${SUDO_UID:-$(id -u)} -g \#${SUDO_GID:-$(id -g)} -- "$@"
}
command="$1"
shift
case "$command" in
up) up "$@" ;;
down) down "$@" ;;
exec) execi "$@" ;;
*) echo "Usage: $0 up|down|exec" >&2; exit 1 ;;
esac
Демонстрация работы скрипта:

Сравнение решений
| Решение | Преимущества | Недостатки |
|---|---|---|
| Замена default | Простота | DHCP отменяет изменения |
| Переопределение default | Не заменяет default | Маршрут забывается при переподключении |
| Правила (rule-based) | Разделение таблиц | Нужно знать IP конечной точки |
| Улучшенные правила (fwmark) | Устойчив к роумингу | Сложнее для понимания |
| Пространства имён | Полная изоляция, чистота | Требует настройки пространств имён |
Преимущества решения с пространствами имён
- Полная изоляция — обычные процессы не видят физические интерфейсы
- Автоматическая маршрутизация — весь трафик идёт через WireGuard без сложных правил
- Прозрачность — не нужно добавлять явные маршруты для конечных точек
- Чистота — нет хаков с таблицами маршрутизации
- Гибкость — можно запускать отдельные процессы в физическом пространстве