ГлавнаяСтатьиПроект YvixSlideCraft

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

203.0.113.0/24 и 198.51.100.0/24 — диапазоны для документации из RFC 5737, в реальной схеме подставьте свои адреса.

Что понадобится

Проверим, что всё на месте:

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

Разберём параметры:

Шаг 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
    }
}

Пояснения:

Имена интерфейсов (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, сможет «запихнуть» его в туннель. Поэтому:

  1. Обязательно ограничивайте ip protocol ipip источником — адресом соседнего маршрутизатора (как в примере выше).
  2. Если данные чувствительные — поверх туннеля поднимите IPsec (transport mode отлично ложится на IP-IP) или используйте WireGuard.
  3. Держите правила forward максимально узкими: только те сети и интерфейсы, которые действительно должны ходить через туннель.

Заключение

IP-IP — отличный рабочий инструмент, когда нужно быстро и без лишних зависимостей связать две сети поверх интернета, а защиту трафика решает что-то поверх. Пара команд из iproute2 — и туннель готов, а nftables добавляет минимально необходимую фильтрацию.