Туннелирование и частные сети
Туннелирование: использование некоторого потока данных для инкапсуляции (в общем случае — произвольного) сетевого трафика.
- Шифрование контента
- Защита от пассивного анализа / фильтрации
- Платформа для включения в инфраструктуру (частные и программно определяемые сети)
Простейший туннель: 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 peer 192.168.11.2 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 peer 192.168.11.1 dev l2tpeth0 # ping 192.168.11.1
На router посмотреть, как ходит UDP (а TCP с прошлого раза зарезан)
Особенности:
- У виртуального интерфейса имеется MAC-адрес!
- Те же упражнения с маршрутом по умолчанию / явным маршрутом до endpoint
В чём разница с ipip?
Если применять bridge («Configure as bridged interfaces» в man-е), можно обойтись одним IP адресом (в простом варианте выше на каждый клиент нужно выделять +1 отдельный IP на сервере)
(если успеем, то пример из man с bridge)
- Правда, интерфейсов на сервере всё равно столько же, сколько клиентов
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 IPForward=yes
/etc/systemd/network/50-intnet.network
[Match] Name=eth1 [Network] Address=10.9.0.1/24 [Route] Gateway=10.9.0.2 Destination=10.0.0.0/8
client:
/etc/systemd/network/50-intnet.network
[Match] Name=eth1 [Network] Address=10.4.0.3/24 Gateway=10.4.0.2
Для title/WireGuard нас сервере нужно
создать виртуальное устройство (с помощью ip link add dev интерфейс type wireguard)
настроить его адрес (обычным ip addr add)
настроить параметры wiregueard-сервера — порт и приватный ключ (с помощью wg set интерфейс listen-port порт private-key /путь/до/файла.key
настроить параметры wiregueard-подключения — открытый ключ и диапазон доступных клиенту адресов (с помощью wg set wg0 peer ключ allowed-ips сеть)
На клиенте:
- создать виртуальное устройство
- настроить его адрес
настроить параметры wiregueard-подключения к серверу — открытый ключ и адрес/порт сервера (с помощью wg set интерфейс peer ключ endpoint адрес:порт)
Для 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-сервер с внутренним DNS-ом.
В нашем случае довольно просто, ибо статика:
Сервер:
- Добавим частную подсеть в NAT
/etc/systemd/network/70-wg.network … IPMasquerade = ipv4
Клиент:
Добавим явный маршрут до сервера частной сети через тот же маршрутизатор, что и по умолчанию
- Добавим ещё явных маршрутов до ключевых локальных сервисов (DNS, файлопомойка, корпоративный мессенджер, whatever)
Только после этого выставим маршрутизатор по умолчанию (в нашем случае статика, поэтому без разницы) на сервер частной сети
При этом либо удаляем старый маршрут по умолчанию, либо делаем так, чтобы метрика новой записи была выше (параметр metric число команды ip route add или Metric= в секции [Route] файла .network).
- Вариант решения:
/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.netdev … AllowedIPs = 0.0.0.0/0 …
- Принимаем от сервера вообще любые пакеты
Сложности:
- Хорошо, если все важные маршруты можно описать просто большим диапазоном (у нас 10/8) — а ну как нельзя?
Или, что хуже, у админов нет фантазии, и обе сети — и локальная, и частная — это 192.168.0.0/24?
Манипуляции с метрикой надо делать над исходным маршрутом, никак не связанным с частной сетью
- Манипуляции с удалением и добавлением маршрутов — это вообще контекстно-зависимый ад
wg-quick как-то решает эту проблему.
В systemd-networkd v250+ вроде бы тоже её решили, но в прошивке этого года — systemd 249.17
- С помощью policy routing и firewall mark
⇒ Нужен разумный best practice. Для начала хотя бы сделать в ядре метрику по умолчанию не 0…
Рашн ВПН
Итак, покупаете вы роутер-мыльницу. Например, 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 на двух клиентах.
К 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
В теме письма должно встречаться слово LinuxNetwork2024