Туннелирование и частные сети
Туннелирование: использование некоторого потока данных для инкапсуляции (в общем случае — произвольного) сетевого трафика.
- Шифрование контента
- Защита от пассивного анализа / фильтрации
- Платформа для включения в инфраструктуру (частные и программно определяемые сети)
Простейший туннель: IP over IP
IP_in_IP: например, для мобильных сетей
Просто добавим ip link соответствующего типа
- С указанием реальных адресов концов туннеля
- Настроим IP как обычно)
Пример на developers.redhat.com (там ещё 100500 вариантов туннелей)
Использование
Обычная схема:
client ← очень-внутренняя-сеть → router ← внутренняя-сеть → srv
- Настроим везде IP-адреса
srv — маршрут на client и конец туннеля (с помощью ip link)
# ip route add очень-внутренняя-сеть via router-IP # ip link add name ipip0 type ipip remote client-IP local srv-IP # ip link set ipip0 up # ip addr add dev ipip0 srv-адрес-туннеля
router — только ip forwarding
client — маршрут по умолчанию и конец туннеля (с помощью ip tunnel, результат тот же)
# ip route add default via router-IP # ip tunnel add ipip0 mode ipip remote srv-IP local client-IP # ip link set ipip0 up # ip addr add dev ipip0 client-адрес-туннеля # ping srv-адрес-туннеля
Посмотреть tcpdump на router
- См. на MTU: мы же запихнули IP в IP ☺
«Выход в интернет»:
- Единственная фишка — конфликт маршрута до конца туннеля и маршрута по умолчанию
На srv
- включим
# dhcpcd eth0
- Настроим NAT
# systemctl enable --now nftables.service # nft add chain inet filter masq "{type nat hook postrouting priority srcnat;}" # nft add rule inet filter masq oif eth0 masquerade
- включим
На client:
- Удалим default route, добавим default route через туннель:
# ip route del default # ip route add default via srv-адрес-туннеля
- Всё отвалится, потому что:
# ip route get srv-IP
⇒ Новый default route не должен распространяться на конец туннеля:
# ip route add srv-IP via router-IP # ping -c3 5.255.255.242
- Удалим default route, добавим default route через туннель:
Проверим, что туннель работает
зарежем TCP на router:
# systemctl enable --now nftables.service # nft add rule inet filter forward ip protocol tcp reject
Какой-нибудь date | netcat 5.255.255.242 80 должен продолжать работать (потому что через router проходит не TCP, а IP)
Вот уже почти VPN! Но:
- На сервере — по интерфейсу для каждого клиента
- Что хуже — каждое соединение требует на сервере отдельного IP-адреса. Если клиентов много, этих IP-адресов на сервере будет много
- ⇒ пригодно в основном для для p2p
- нет шифрования и авторизации
ipip0 — «не совсем настоящий» интерфейс (только p2p, без MA и т. п.)
l2tp — фреймы в IP или UDP
Можно туннелировать не IP-пакеты, а фреймы — например, при помощи L2TP.
Всё по документации — скажем, с инкапсуляцией не в IP, а в UDP; и для простоты — без bridge
- Общая схема:
srv, туннель № 300 , порт 5000 ←→ client, туннель № 400, порт 6000
srv, сеанс № 100 ←→ client, сеанс № 200
На srv (после настройки eth0 и маршрута до client):
# ip l2tp add tunnel tunnel_id 300 peer_tunnel_id 400 encap udp local srv-IP remote client-IP udp_sport 5000 udp_dport 6000 # ip l2tp add session tunnel_id 300 session_id 100 peer_session_id 200 # ip l2tp show tunnel # ip l2tp show session # ip link set l2tpeth0 up # ip addr add 192.168.11.1/24 dev l2tpeth0
На client (после настройки маршрута до srv) всё то же самое, кроме перестановки ID и портов в парах:
# ip l2tp add tunnel tunnel_id 400 peer_tunnel_id 300 encap udp local client-IP remote srv-IP udp_sport 6000 udp_dport 5000 # ip l2tp add session tunnel_id 400 session_id 200 peer_session_id 100 # ip l2tp show tunnel # ip l2tp show session # ip link set l2tpeth0 up # ip addr add 192.168.11.2/24 l2tpeth0 # ping 192.168.11.1
На router посмотреть, как ходит UDP (а TCP с прошлого раза зарезан)
Особенности:
- У виртуального интерфейса имеется MAC-адрес — это ещё больше похоже на VPN!
Один серверный IP:
подключения определяются сеансами (session), полностью независимыми от уровня IP;
сеансы уложены в тоннель (tunnel), спецификация которого состоит аж из шести чисел (ID тоннеля, IP-адрес и порт с обеих сторон)
- Сеансами в таблице удобно манипулировать автоматически
- Всё ещё никакого шифрования / защиты от пассивного анализа
Виртуальный интерфейс l2tpeth можно добавить в bridge c локальным интерфейсом — тогда абоненты из туннелей окажутся в одном сегменте с абонентами локальной сети
Как и в случае ipip, «выход в интернет» посредством l2tp требует
настроить маршрут по умолчанию внутрь тоннеля (в нашем примере — через 192.168.11.1),
но сохранить (или воспроизвести) более приоритетный маршрут до конца туннеля (server-IP) в обход этого туннеля (в нашем примере — ip route add srv-IP via router-IP)
Wireguard
- Идея та же самая: UDP-пакеты с каким-то payload
- Защита асимметричным шифрованием
- нужны пары открытый/закрытый ключ от всех участников процесса — и от сервера, и от клиентов
- дополнительно можно ключ защитить паролем
- обычно генерирует и раздаёт админ
- Есть клиенты под всякие архитектуры
Linux: управляется утилитой wg
Лайфхак для копипасты:
# wg genkey | tee /dev/stderr | wg pubkey
wg genkey — генрирует секретный ключ
| wg pubkey — генерирует из него открытый ключ
| tee /dev/stderr — дублирует секретный ключ на stderr
Минимальная настройка: воспользуемся systemd-networkd:
srv:
/etc/systemd/network/20-default.network
[Match] Name = eth0 [Network] DHCP = ipv4 IPv4Forwarding=yes
/etc/systemd/network/50-intnet.network
[Match] Name=eth1 [Network] Address=10.9.0.1/24 [Route] Gateway=10.9.0.27 Destination=10.0.0.0/8
router — выполняет роль интернета, так что вполне годится autonet
client:
/etc/systemd/network/50-intnet.network
[Match] Name=eth1 [Network] Address=10.4.0.3/24 Gateway=10.4.0.27
Ручная настройка
Для настройки вручную на сервере нужно
- создать виртуальное устройство
# ip link add dev interface type wireguard
- настроить его адрес
# ip addr add …
- настроить параметры wireguard-сервера — порт и приватный ключ
# wg set interface listen-port port private-key /path/to/file.key
- настроить параметры wireguard-подключения — открытый ключ и диапазон доступных клиенту адресов
# wg set interface peer key allowed-ips network
На клиенте:
- создать виртуальное устройство
- настроить его адрес
- настроить приватный ключ
# wg set interface private-key /path/to/file.key
- настроить параметры wiregueard-подключения к серверу — открытый ключ и адрес/порт сервера
# wg set interface peer key endpoint address:port
Настройка средствами networkd
Для systemd-networkd:
Сервер:
Создать и настроить устройство — /etc/systemd/network/70-wg.netdev
[NetDev] Name = wg Kind = wireguard [WireGuard] ListenPort = порт PrivateKey = сервера, никому не показывать (возможно, лучше PrivateKeyFile = …) # PublicKey = сервера, просто лежит тут про запас [WireGuardPeer] AllowedIPs = 192.168.111.5/32 PublicKey = клиента
PrivateKey = — плохая идея (как минимум надо сделать chgrp systemd-network и chmod o-r)
Настроить сеть — /etc/systemd/network/70-wg.network
[Match] Name = wg [Network] Address = 192.168.111.1/24
Клиент:
/etc/systemd/network/70-wg.netdev
[NetDev] Name = wg Kind = wireguard [WireGuard] PrivateKey = этого клиента # PublicKey = этого клиента, про запас [WireGuardPeer] AllowedIPs = 192.168.111.0/24 PublicKey = сервера Endpoint = адрес:порт
/etc/systemd/network/70-wg.network
[Match] Name = wg [Network] Address = 192.168.111.5/24
Частная сеть как выход в интернет
Ещё раз: проблема сохранения исходного маршрута до tunnel endpoint. Задача довольно просто формулируется, но полна нюансов:
- При внезапном перенаправлении всего роутинга в в туннель надо умудриться не направить туда роутинг до end point-а.
Вообще любые нелокальные маршруты, про которые знал внутренний маршрутизатор, но не знает endpoint. Например, внутренний DNS-сервер.
В нашем случае довольно просто, ибо статика:
Сервер:
- Добавим частную подсеть в NAT
/etc/systemd/network/70-wg.network … [Network] IPMasquerade = ipv4
Клиент:
Вместо локального диапазона вписываем в AllowedIPs весь интернет:
/etc/systemd/network/70-wg.netdev … [WireGuardPeer] AllowedIPs = 0.0.0.0/0 …
- Добавим ещё явных маршрутов до ключевых локальных сервисов (DNS, файлопомойка, корпоративный мессенджер, whatever)
Только после этого выставим маршрутизатор по умолчанию (в нашем случае статика, поэтому без разницы) на сервер частной сети
При этом либо удаляем старый маршрут по умолчанию, либо делаем так, чтобы метрика новой записи была выше (параметр metric число команды ip route add или Metric= в секции [Route] файла .network).
Вариант с метрикой маршрута
- Заранее понизим метрику «маршрута по умолчанию без VPN», если он есть
Добавим явный маршрут до сервера частной сети (и другие нелокальные маршруты) через тот же маршрутизатор
/etc/systemd/network/50-intnet.network … [Route] Gateway = 10.4.0.2 Metric = 100 [Route] Gateway = 10.4.0.2 Destination = 10.9.0.1
- Смело вписываем «маршрут по умолчанию через VPN»:
/etc/systemd/network/70-wg.network [Network] Gateway = 192.168.111.1 …
Сложности:
- Маршрут получен по DHCP — нельзя задать метрику
- Манипуляции с динамическим удалением и добавлением маршрутов — это вообще контекстно-зависимый ад
Пересечение адресных пространств: в сети, из которой мы пытаемся подключиться, те же адреса, что и у VPN, т. е. 192.168.111.0/24 вместо 10/8
Манипуляции с метрикой надо делать над исходным маршрутом, никак не связанным с частной сетью
⇒ Нужен разумный best practice. Для начала хотя бы сделать в ядре метрику по умолчанию не 0…
Вариант с целевой маршрутизацией
«Маршрут по умолчанию через тоннель» записываем в другую таблицу
- Добавляем правило использовать основную таблицу для пакетов, идущих на VPN-сервер (и другие нелокальные внутренние адреса)
- Добавляем правило использовать «другую таблицу» для всех остальных пакетов
/etc/systemd/network/70-wg.network … [Route] Table = 192 Gateway = 192.168.111.1 [RoutingPolicyRule] To = 10.0.0.0/8 Table = main [RoutingPolicyRule] Table = 192
Удобство:
- Не нужно модифицировать основную настройку
Сложности:
В 70-wg.network используются данные текущей настройки сети — если они меняются, придётся туда лезть
Хорошо, если все важные маршруты можно описать просто большим диапазоном (у нас 10/8), — а ну как нельзя?
- Пересечение адресных пространств игнорировать нельзя
wg-quick как-то решает эту проблему.
Innernet, кажется, не решает.
Рашн ВПН
Итак, покупаете вы роутер-мыльницу. Например, DLink. Настраиваете в нём доступ в интернет, например, по l2tp в каком-нибудь большом операторе. И выясняется, что
До настройки l2tp default route смотрел куда-то внутрь приватных сетей оператора, на некоторый внутренний маршрутизатор. После настройки он смотрит внутрь l2tp-туннеля.
Проблема в том, что противоположный конец l2tp-туннеля не находится в локальной сети, так что после смены default route l2tp-пакеты перестают до него доходить.
Нужно сделать так, чтобы маршрут по умолчанию смотрел на конец туннеля, а маршрут до конца туннеля — туда же, куда и раньше.
Если вам повезёт, и в мыльнице есть графа «static routes», а адрес внутреннего маршрутизатора и конца тоннеля не меняются, этот маршрут можно туда забить
- Однако DNS всё ещё не работает, потому что он тоже где-то внутри сети провайдера, но не в локальной сети.
- Хорошо, если вам разрешено пользоваться 8.8.8.8 или 1.1.1.1; правда, все внутренние имена хостов провайдера больше не имена
- Хорошо также, если адрес DNS-сервера всегда одинаковый — маршрут до него можно забить туда же в «static routes»
Но вам не повезло. Адрес DNS-сервера вы получаете по DHCP только после настройки туннеля (а тот, который был раньше, работает только на внутренние ресурсы), и он время от времени разный.
Так что вы идёте на форум, и скачиваете оттуда огромную таблицу маршрутизации, в которую сообщество вписывает все актуальные маршруты на концы тоннеля и на DNS-сервера, которые бывают у этого провайдера, заливаете её в мыльницу, и она чудесным образом работает. Или не работает.
Отчаявшись, вы находите в том же форуме рецепт. Надо зайти на сайт российского DLink, скачать оттуда специальную прошивку и залить её. После прошивания в настройках l2tp появляется — я не вру! — галочка «Russian VPN». И с ней всё работает!
Russian VPN — это просто несколько shell-скриптов (на мыльнице Linux же), которые делают вот что:
Запоминают маршрутизатор по умолчанию до настройки l2tp
- Настраивают l2tp-тоннель и IP-вадреса стандартным способом
Смотрят адрес DNS-сервера (он приехал по DHCP) и добавляют в таблицу маршрутизации маршрут до него и до конца туннеля через старый маршрутизатор
Всякое
IPsec — безудержно. Но работает!
- Основная фишка: транспортный режим, в котором шифруется только payload от IP…
- …идея была в том, чтобы логика сети оставалась, а в пакеты никто не заглядывал…
- …и она оказалась провальной: даже NAT не работает
- Тогда придумали туннельный режим. А он как всё такие туннели, только сложнее в поддержке.
- Основная фишка: транспортный режим, в котором шифруется только payload от IP…
- Если не wireguard, то OpenVPN — давно и проверенно работает
Всевозможные «платные VPN-сервисы», как правило — модификация уже известных протоколов.
Д/З
Образ не изменился
Настроить выход в интернет через VPN на двух клиентах.
К srv из двух разных сетей (eth1 и eth2) подключены два клиента. VPN-адреса (на виртуальном wireguard-интерфейсе) у них из одной сети, и только пакеты из этой сети NAT-ятся наружу (через eth0, использовать nftables). Должен работать также DNS (любым способом) и доступ от одного клиента к другому.
- Отчёт:
report 11 srv
networkctl status -a -n0 --no-pager
nft list ruleset
host ya.ru
ip route
wg
report 11 client и report 11 client2
networkctl status -a -n0 --no-pager
host ya.ru
ip route
wg
ping -c3 адрес_другого_клиента
Три отчёта (названия сохранить, должно быть: report.11.srv, report.11.client и report.11.client2) переслать одним письмом в качестве приложений на uneexlectures@cs.msu.ru
В теме письма должно встречаться слово LinuxNetwork2025