IP-IP туннель между Linux-маршрутизаторами
IP-IP (он же IP-in-IP, протокол 4) — самый простой из туннелей: обычный IPv4-пакет целиком кладётся внутрь другого IPv4-пакета. Наружу «торчат» только адреса двух маршрутизаторов, а внутренние сети общаются между собой так, будто между ними проложен прямой кабель.
Плюсы: минимум накладных расходов (всего 20 байт внешнего заголовка), нет пользовательских демонов — всё делается штатными средствами ядра через iproute2, настраивается за пару минут.
Минусы: трафик не шифруется и не аутентифицируется. Если данные чувствительные — поверх IP-IP стоит поднять IPsec или сразу взять WireGuard. Зато как транспорт для внутренней маршрутизации IP-IP очень удобен.
Схема сети
Рассмотрим два офиса, каждый из которых подключён к интернету через Linux-маршрутизатор.
LAN A: 192.168.1.0/24 LAN B: 192.168.2.0/24
| |
[ Router A ] ------ IP-IP туннель ------ [ Router B ]
WAN: 203.0.113.10 WAN: 198.51.100.20
tun0: 10.255.0.1/30 tun0: 10.255.0.2/30
- Router A: внешний адрес
203.0.113.10, локальная сеть192.168.1.0/24. - Router B: внешний адрес
198.51.100.20, локальная сеть192.168.2.0/24. - Для туннеля выделим служебную подсеть
10.255.0.0/30.
203.0.113.0/24и198.51.100.0/24— диапазоны для документации из RFC 5737, в реальной схеме подставьте свои адреса.
Что понадобится
- Два Linux-маршрутизатора с белыми (или хотя бы достижимыми друг для друга) IP-адресами.
- Пакет
iproute2(утилитыip,tcи т. д.) — есть в любом современном дистрибутиве. nftablesдля фильтрации и форвардинга.- Права root.
Проверим, что всё на месте:
ip -V
nft -v
Шаг 1. Включаем маршрутизацию
По умолчанию ядро не пересылает пакеты между интерфейсами. Разрешаем форвардинг на обоих маршрутизаторах:
echo 'net.ipv4.ip_forward = 1' > /etc/sysctl.d/99-ip-forward.conf
sysctl --system
Проверить можно так:
sysctl net.ipv4.ip_forward
Должно вывести net.ipv4.ip_forward = 1.
Шаг 2. Создаём туннель
На Router A:
ip tunnel add tun0 mode ipip remote 198.51.100.20 local 203.0.113.10 ttl 64
ip addr add 10.255.0.1/30 dev tun0
ip link set tun0 up
На Router B:
ip tunnel add tun0 mode ipip remote 203.0.113.10 local 198.51.100.20 ttl 64
ip addr add 10.255.0.2/30 dev tun0
ip link set tun0 up
Разберём параметры:
mode ipip— тип туннеля IP-in-IP (протокол 4).remote— внешний адрес противоположного маршрутизатора.local— наш собственный внешний адрес.ttl— значение TTL для внешнего заголовка. Если оставить0, ядро унаследует TTL от внутреннего пакета. Явное значение полезно, чтобы скрыть «вложенность» отtracerouteи не ломать PMTUD в некоторых случаях.
Шаг 3. Проверяем связность
ip -d link show tun0
ip addr show tun0
ping -c 3 10.255.0.2
ip -d link show tun0 должен вывести что-то вроде ipip remote 198.51.100.20 local 203.0.113.10 ttl 64.
Теперь пингуем соседний конец туннеля (с Router A). Если пинг не проходит — сначала проверьте, что между внешними адресами вообще есть связность (ping 198.51.100.20 с Router A), и что провайдер или промежуточный файрвол не режет протокол 4.
Шаг 4. Маршруты до удалённых сетей
Туннель поднят, но маршрутизаторы ещё не знают, что за ним находятся локальные сети друг друга. Добавляем статические маршруты.
На Router A:
ip route add 192.168.2.0/24 via 10.255.0.2 dev tun0
На Router B:
ip route add 192.168.1.0/24 via 10.255.0.1 dev tun0
Теперь хост из 192.168.1.0/24 должен пинговать хост из 192.168.2.0/24 — при условии, что на хостах маршрут по умолчанию указывает на свой маршрутизатор, а на самих маршрутизаторах форвардинг разрешён и не блокируется файрволом.
Шаг 5. Настраиваем nftables
К этому моменту всё уже работает, если файрвол отсутствует или разрешает всё. На практике фильтрация нужна. Приведём минимальный рабочий набор правил для Router A (для Router B — зеркально поменяйте адреса). Файл /etc/nftables.conf:
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
iif "lo" accept
ct state established,related accept
# IP-IP принимаем только от соседнего маршрутизатора
iifname "wan0" ip saddr 198.51.100.20 ip protocol ipip accept
# SSH для управления (сузьте источник под себя)
iifname "wan0" ip saddr 203.0.113.0/24 tcp dport 22 accept
counter drop
}
chain forward {
type filter hook forward priority filter; policy drop;
ct state established,related accept
# Фиксируем MSS под MTU туннеля, чтобы не было фрагментации
tcp flags syn tcp option maxseg size set rt mtu
# Разрешаем трафик между локальной сетью и туннелем
iifname "lan0" oifname "tun0" accept
iifname "tun0" oifname "lan0" accept
counter drop
}
}
Пояснения:
ip protocol ipip— это как раз протокол 4 (IP-in-IP). Именно такие пакеты и являются «телом» туннеля.- В
inputмы принимаем IP-IP только от198.51.100.20— это не шифрование, но хотя бы не даёт посторонним слать пакеты в наш туннель. - В
forwardразрешаем обмен только междуlan0иtun0. Всё остальное (например, прямой форвардинг изwan0вlan0мимо туннеля) отбрасывается. tcp flags syn tcp option maxseg size set rt mtuподгоняет MSS TCP под MTU маршрута — подробнее в следующем шаге.
Имена интерфейсов (wan0, lan0) замените на свои. Применяем:
nft -f /etc/nftables.conf
Если одна из сторон не имеет обратного маршрута на другую (например, одна сеть «за NAT»), можно добавить маскарадинг исходящего трафика:
table inet nat {
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
iifname "lan0" oifname "tun0" masquerade
}
}
Шаг 6. MTU и MSS
IP-IP добавляет ещё один IPv4-заголовок (20 байт). Если MTU внешнего канала 1500, то внутренний пакет не может быть больше 1480 — иначе он начнёт фрагментироваться, а фрагментация в туннелях часто приводит к трудноуловимым проблемам.
Проще всего сразу уменьшить MTU туннеля с запасом:
ip link set tun0 mtu 1400
Сделайте это на обоих концах. А правило MSS из предыдущего шага (maxseg size set rt mtu) автоматически уменьшит размер сегмента TCP под фактический MTU маршрута, чтобы TCP-соединения не страдали от фрагментации.
Шаг 7. Делаем настройки постоянными
Пока что все команды живут до перезагрузки. На Debian/Ubuntu туннель и маршрут можно прописать в /etc/network/interfaces (на Router A):
auto tun0
iface tun0 inet static
address 10.255.0.1/30
pre-up ip tunnel add tun0 mode ipip remote 198.51.100.20 local 203.0.113.10 ttl 64
up ip link set tun0 mtu 1400
post-up ip route add 192.168.2.0/24 via 10.255.0.2 dev tun0
post-down ip tunnel del tun0
На systemd-дистрибутивах удобнее systemd-networkd: туннель описывается файлами .netdev + .network. Принцип тот же, просто другой синтаксис.
net.ipv4.ip_forward мы уже сделали постоянным через sysctl на шаге 1, а nftables — через /etc/nftables.conf и автозапуск службы nftables.
Безопасность
Повторю главное: IP-IP сам по себе ничего не защищает. Любой, кто сможет отправить на ваш внешний адрес пакет протокола 4, сможет «запихнуть» его в туннель. Поэтому:
- Обязательно ограничивайте
ip protocol ipipисточником — адресом соседнего маршрутизатора (как в примере выше). - Если данные чувствительные — поверх туннеля поднимите IPsec (transport mode отлично ложится на IP-IP) или используйте WireGuard.
- Держите правила
forwardмаксимально узкими: только те сети и интерфейсы, которые действительно должны ходить через туннель.
Заключение
IP-IP — отличный рабочий инструмент, когда нужно быстро и без лишних зависимостей связать две сети поверх интернета, а защиту трафика решает что-то поверх. Пара команд из iproute2 — и туннель готов, а nftables добавляет минимально необходимую фильтрацию.