Туннелирование и частные сети
Туннелирование: использование некоторого потока данных для инкапсуляции (в общем случае — произвольного) сетевого трафика.
- Шифрование контента
- Защита от пассивного анализа / фильтрации
- Платформа для включения в инфраструктуру (частные и программно определяемые сети)
Простейший туннель: IP over IP
Замечание в сторону: SRv6 в действительности тоже решает похожую задачу: можно прицельно доставлять пакеты до определённого узла, а вторым узлом в заголовке (SRH) указать актуального адресата. Однако для собственно туннелирования SRv6 обычно не используется (можно поэкспериментировать самому).
TODO Заменить «поверх» (в значении «over») в тексте на «внутри» (хотя это и почти антонимы, но ассоциация с «поверхность» сильно сбивает с толку).
Использование
IP6 over IP6 — это как-то скучно! Ради большей наглядности пустим IP4 поверх IP6.
Обычная схема:
client ← очень-внутренняя-сеть → router ← внутренняя-сеть → srv
Настроим везде IP-адреса, на router подняв RAdv
srv — маршрут на client и конец туннеля (с помощью ip tunnel)
router — только IPv6 forwarding
client — маршрут по умолчанию и конец туннеля (с помощью ip link, результат тот же)
[root@router ~]# sysctl net.ipv{6,4}.conf.all.forwarding
net.ipv6.conf.all.forwarding = 1
net.ipv4.conf.all.forwarding = 0Настроим передачу IPv4 в тоннеле:
[root@client ~]# ip -6 -br a show scope global eth1 UP 2a00:f480:8:cc5:a00:27ff:fe1a:ffa/64 [root@client ~]# ip link add name over0 type ip6tnl mode ipip6 local 2a00:f480:8:cc5:a00:27ff:fe1a:ffa remote 2a00:f480:8:cde:a00:27ff:fee3:f50e [root@client ~]# ip link set over0 up [root@client ~]# ip a add 10.50.0.1/24 dev over0 # это IPv4; здесь весь префикс on-link, и назначился маршрут на over0 [root@client ~]# ip -d link show over0 <...>
[root@srv ~]# ip -6 -br a lo UNKNOWN ::1/128 eth1 UP 2a00:f480:8:cde:a00:27ff:fee3:f50e/64 fe80::a00:27ff:fee3:f50e/64 [root@srv ~]# ip tunnel add over0 mode ipip6 local 2a00:f480:8:cde:a00:27ff:fee3:f50e remote 2a00:f480:8:cc5:a00:27ff:fe1a:ffa [root@srv ~]# ip link set over0 up [root@srv ~]# ip a add 10.50.0.2/24 dev over0 # это IPv4; здесь весь префикс on-link, и назначился маршрут на over0 [root@srv ~]# ip -d link show over0 <...>
- См. на MTU: мы же запихнули IP в IP ☺
Посмотреть tcpdump на router с клиента на srv с srv на клиента
Ядро Linux приписывает к внешнему заголовку destination option "Tunnel Encap Limit" rfc2473, равный 4.
TODO Даже tshark тянет over 120 Мб собственной библиотеки. Предложить скачать результат запуска tcpdump -w и посмотреть на хосте с помощью хостового wireshark?
Паста из wireshark:
Frame 1: Packet, 146 bytes on wire (1168 bits), 146 bytes captured (1168 bits)
Ethernet II, Src: PCSSystemtec_1a:0f:fa (08:00:27:1a:0f:fa), Dst: PCSSystemtec_d7:92:2a (08:00:27:d7:92:2a)
Destination: PCSSystemtec_d7:92:2a (08:00:27:d7:92:2a)
Source: PCSSystemtec_1a:0f:fa (08:00:27:1a:0f:fa)
Type: IPv6 (0x86dd)
Internet Protocol Version 6, Src: 2a00:f480:8:cc5:a00:27ff:fe1a:ffa, Dst: 2a00:f480:8:cde:a00:27ff:fee3:f50e
0110 .... = Version: 6
.... 0000 0000 .... .... .... .... .... = Traffic Class: 0x00 (DSCP: CS0, ECN: Not-ECT)
.... 0001 1011 0001 1000 1110 = Flow Label: 0x1b18e
Payload Length: 92
Next Header: Destination Options for IPv6 (60)
Hop Limit: 64
Source Address: 2a00:f480:8:cc5:a00:27ff:fe1a:ffa
Destination Address: 2a00:f480:8:cde:a00:27ff:fee3:f50e
[Source SLAAC MAC: PCSSystemtec_1a:0f:fa (08:00:27:1a:0f:fa)]
[Destination SLAAC MAC: PCSSystemtec_e3:f5:0e (08:00:27:e3:f5:0e)]
Destination Options for IPv6
Next Header: IPIP (4)
Length: 0
[Length: 8 bytes]
Tunnel Encapsulation Limit
Type: Tunnel Encapsulation Limit (0x04)
Length: 1
Tunnel Encapsulation Limit: 4
PadN
Type: PadN (0x01)
Length: 1
PadN: 00
Internet Protocol Version 4, Src: 10.50.0.1, Dst: 10.50.0.2
Internet Control Message ProtocolСреди опций для узла назначения:
- Лимит на количество инкапсуляций: подобно Hop Limit, защита от зацикливания
Проверим, что туннель работает
зарежем TCP на router:
# systemctl enable --now nftables.service # nft add rule inet filter forward meta l4proto tcp reject
Какой-нибудь date | netcat 10.50.0.242 80 должен продолжать работать (потому что через router проходит не TCP, а IP)
Недостатки
- На сервере — по интерфейсу для каждого клиента (и наоборот)
- Каждое соединение требует на сервере отдельного IP-адреса
- Если клиентов много, этих IP-адресов на сервере будет много. Их утомительно настраивать
- ⇒ пригодно в основном для p2p
- нет шифрования и авторизации; если нас смаршрутизировали, то мы молодец
TODO разъяснить: over0 — «не совсем настоящий» интерфейс (только p2p, без MAC и т. п.)
IP6 over TCP «на скорую руку» с socat
В socat есть работа с tun/tap устройствами. Буквально в одну строку из них делается полноценный тоннель:
[root@srv ~]# socat TCP6-LISTEN:1337,fork,reuseaddr TUN,tun-name=cli0,up </dev/null &
и
[root@client ~]# socat TCP:сервер:1337 TUN,tun-name=srv0,up </dev/null &
Результат:
[root@srv ~]# ip -6 -br a show dev cli0 cli0 UNKNOWN fe80::e89a:f475:389f:48cd/64
[root@client ~]# ip -6 r show dev srv0 fe80::/64 proto kernel metric 256 pref medium
[root@client ~]# ping -c2 fe80::e89a:f475:389f:48cd%srv0 PING fe80::e89a:f475:389f:48cd%srv0 (fe80::e89a:f475:389f:48cd%srv0) 56 data bytes 64 bytes from fe80::e89a:f475:389f:48cd%srv0: icmp_seq=1 ttl=64 time=0.338 ms 64 bytes from fe80::e89a:f475:389f:48cd%srv0: icmp_seq=2 ttl=64 time=0.339 ms --- fe80::e89a:f475:389f:48cd%srv0 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 1001ms rtt min/avg/max/mdev = 0.338/0.338/0.339/0.000 ms
* посмотреть tcpdump на router
l2tp — фреймы в IP или UDP
TODO В идеале — вообще не использовать IP-адреса, а прокинуть что-то, чему нужен непосредственно L2. Если не получится — всё-таки заменить IP4 на IP6.
Можно туннелировать не IP-пакеты, а фреймы — например, при помощи L2TP.
Всё по документации — скажем, с инкапсуляцией не в IP, а в UDP; и для простоты — без bridge
- Общая схема:
srv, туннель № 300 , порт 1337 ←→ client, туннель № 400, порт 1337
srv, сеанс № 1 ←→ client, сеанс № 2
На srv (после настройки eth0 и маршрута до client):
# ip l2tp add tunnel tunnel_id 300 peer_tunnel_id 400 encap udp local srv-IP remote client-IP udp_sport 1337 udp_dport 1337 # ip l2tp add session tunnel_id 300 session_id 1 peer_session_id 2 # 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 1337 udp_dport 1337 # ip l2tp add session tunnel_id 400 session_id 2 peer_session_id 1 # 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-адрес — это практически настоящий виртуальный Ethernet-сегмент
Один серверный IP:
подключения определяются сеансами (session), полностью независимыми от уровня IP;
сеансы уложены в тоннель (tunnel), спецификация которого состоит аж из шести чисел (ID тоннеля, IP-адрес и порт с обеих сторон)
- Сеансами в таблице удобно манипулировать автоматически
- Всё ещё никакого шифрования / защиты от пассивного анализа
Виртуальный интерфейс l2tpeth можно добавить в bridge c локальным интерфейсом — тогда абоненты из туннелей окажутся в одном сегменте с абонентами локальной сети
WireGuard
- Идея та же самая: UDP-пакеты с каким-то payload
- Защита асимметричным шифрованием
- нужны пары открытый/закрытый ключ от всех участников процесса — и от сервера, и от клиентов
- дополнительно можно ключ защитить паролем
- обычно генерирует и раздаёт админ
- Есть клиенты под всякие архитектуры
Linux: управляется утилитой wg
Начальная настройка:
[root@srv ~]# cat /etc/systemd/network/50-intnet.network [Match] Name=eth1 [IPv6AcceptRA] Token=::918
[root@router ~]# cat /etc/systemd/network/50-intnet.network [Match] Name=eth1 [Network] IPv6SendRA=yes Address=2a00:f480:8:e50::919/64 [IPv6Prefix] Prefix=2a00:f480:8:e50::/64 [root@router ~]# cat /etc/systemd/network/60-deepnet.network [Match] Name=eth2 [Network] IPv6SendRA=yes Address=2a00:f480:8:e60::919/64 [IPv6Prefix] Prefix=2a00:f480:8:e60::/64 [root@router ~]# cat /etc/systemd/networkd.conf [Network] IPv6Forwarding=yes
[root@client ~]# cat /etc/systemd/network/60-deepnet.network [Match] Name=eth1 [IPv6AcceptRA] Token=::91a
Ручная настройка
Лайфхак для копипасты:
# wg genkey | tee /dev/stderr | wg pubkey
wg genkey — генерирует секретный ключ
| wg pubkey — генерирует из него открытый ключ
| tee /dev/stderr — дублирует секретный ключ на stderr
Для настройки вручную на сервере нужно
- создать виртуальное устройство
# 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
- настроить параметры wireguard-подключения к серверу — открытый ключ и адрес/порт сервера
# wg set interface peer key allowed-ips network endpoint address:port
[root@athena ~]# ip link add dev wg0 type wireguard [root@athena ~]# ip a add 2a00:f480:8:b::a/64 dev wg0 [root@athena ~]# ip -br l wg0 DOWN <POINTOPOINT,NOARP> [root@srv ~]# ip link add dev wg0 type wireguard [root@srv ~]# ip a add 2a00:f480:8:b::c/64 dev wg0 [root@srv ~]# ip -br l wg0 DOWN <POINTOPOINT,NOARP>
[root@athena ~]# wg genkey | tee /etc/wireguard/secret.key | wg pubkey FYmBjluPBjLW5YWjAdPGeaVKupiwGpkWepelFNKI5RE= [root@athena ~]# chmod go-rwx /etc/wireguard/secret.key [root@srv ~]# wg genkey | tee /etc/wireguard/secret.key | wg pubkey Qi5gNRivObZUEUWocQPAct5a7MQxOY6MrUPyyqGeMS8= [root@srv ~]# chmod go-rwx /etc/wireguard/secret.key
[root@srv ~]# wg set wg0 listen-port 51820 private-key /etc/wireguard/secret.key [root@srv ~]# wg set wg0 peer 'FYmBjluPBjLW5YWjAdPGeaVKupiwGpkWepelFNKI5RE=' allowed-ips 2a00:f480:8::/48 [root@athena ~]# wg set wg0 private-key /etc/wireguard/secret.key # neither '2001:db8:0:c::91a' nor '[2001:db8:0:c::91a]' are sufficient as endpoint [root@athena ~]# wg set wg0 peer 'Qi5gNRivObZUEUWocQPAct5a7MQxOY6MrUPyyqGeMS8=' endpoint '[2001:db8:0:c::91a]:51820' allowed-ips 2a00:f480:8::/48 [root@athena ~]# ip link set wg0 up [root@srv ~]# ip link set wg0 up
Туннель установлен!
[root@athena ~]# ping -c4 -i0.4 2a00:f480:8:b::c PING 2a00:f480:8:b::c (2a00:f480:8:b::c) 56 data bytes 64 bytes from 2a00:f480:8:b::c: icmp_seq=1 ttl=64 time=9.39 ms 64 bytes from 2a00:f480:8:b::c: icmp_seq=2 ttl=64 time=2.53 ms 64 bytes from 2a00:f480:8:b::c: icmp_seq=3 ttl=64 time=2.80 ms 64 bytes from 2a00:f480:8:b::c: icmp_seq=4 ttl=64 time=2.84 ms --- 2a00:f480:8:b::c ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 1204ms rtt min/avg/max/mdev = 2.526/4.389/9.392/2.890 ms
Настройка средствами networkd
Для systemd-networkd:
Сервер:
- Создать файл с приватным ключом, ограничить ему доступ:
[root@ ]# wg genkey > /etc/systemd/network/70-wg-private.key [root@ ]# chmod ugo-w,o-r /etc/systemd/network/70-wg-private.key [root@ ]# chown root:systemd-network /etc/systemd/network/70-wg-private.key
никому его не показывать! Создать и настроить устройство — /etc/systemd/network/70-wg.netdev
[NetDev] Name = wg Kind = wireguard [WireGuard] ListenPort = порт PrivateKeyFile = /etc/systemd/network/70-wg-private.key # PublicKey = сервера, просто лежит тут про запас [WireGuardPeer] AllowedIPs = 2a00:f480:8::/48 PublicKey = клиента
Настроить сеть — /etc/systemd/network/70-wg.network
[Match] Name = wg [Network] Address = 2a00:f480:8:b::c/64
Клиент:
- Создать файл с приватным ключом, ограничить ему доступ:
[root@ ]# wg genkey > /etc/systemd/network/70-wg-private.key [root@ ]# chmod ugo-w,o-r /etc/systemd/network/70-wg-private.key [root@ ]# chown root:systemd-network /etc/systemd/network/70-wg-private.key
/etc/systemd/network/70-wg.netdev
[NetDev] Name = wg Kind = wireguard [WireGuard] PrivateKeyFile = /etc/systemd/network/70-wg-private.key # PublicKey = этого клиента, про запас [WireGuardPeer] AllowedIPs = 2a00:f480:8::/48 PublicKey = сервера Endpoint = адрес:порт
/etc/systemd/network/70-wg.network
[Match] Name = wg [Network] Address = 2a00:f480:8:b::a/64
Поля PublicKey = клиента (и сервера) заполняются из вывода wg pubkey < /etc/systemd/network/70-wg-private.key
на том хосте, чей ключ!
Д/З
wg — route encap/decap — наблюдатель — route encap/decap — wg
TODO: включить в конфиге ядра ALT MPLS-маршрутизацию
[root@athena ~]# ip route add 0.0.0.0/0 encap mpls 102 via inet6 2001:db8:0:a::919 [root@router ~]# ip -M route add 102 as 203 via inet6 2001:db8:0:c::91a RTNETLINK answers: Operation not supported [root@srv ~]# ip -M route add 203 dev lo RTNETLINK answers: Operation not supported
