Перейти к содержимому

Все статьи

Route-based IPsec на MikroTik: xfrm-интерфейсы в RouterOS 7.25

Hub-and-spoke на IPsec без policy на каждую подсеть: туннель становится интерфейсом, маршруты приходят по BGP, отказ ловит BFD.

· MikroTik, RouterOS 7.25, IPsec, BGP

До RouterOS 7.25 IPsec на MikroTik был только policy-based: какой трафик шифровать, решали политики с парами подсетей. Каждая новая сеть за туннелем — это новая policy на обеих сторонах, исключения из NAT и fasttrack, а динамическая маршрутизация поверх голого IPsec невозможна — приходилось заворачивать GRE или IPIP внутрь.

В 7.25 появились xfrm-интерфейсы — тот же механизм, что в ядре Linux. Политика IPsec привязывается к интерфейсу, а что в него отправить, решает обычная таблица маршрутизации. Туннель ведёт себя как любой другой линк: на нём есть адрес, через него идут маршруты, на нём работают BGP, OSPF и BFD, его можно указать в firewall.

Перед началом: firewall для IPsec

Сначала база. IPsec — это не один порт, а три вида трафика, и каждый нужно явно разрешить в цепочке input на WAN:

  • UDP 500 — IKE. На нём стороны договариваются об алгоритмах, проверяют PSK или сертификаты и создают SA. Без него туннель не начнёт подниматься: в логе будут повторные попытки и таймауты.
  • UDP 4500 — NAT-T. Если IKE обнаружил NAT между сторонами, дальнейший IKE и весь ESP идут внутри UDP 4500. Для споков за PPPoE и LTE с провайдерским NAT это основной порт.
  • ESP — IP-протокол 50 (protocol=ipsec-esp), а не порт TCP или UDP. В нём едут сами зашифрованные пакеты, если NAT между сторонами нет. Забытое правило для ESP — классическая ситуация «IKE поднялся, SA есть, а трафик не идёт».

Правила должны стоять выше стандартного drop всего, что пришло из WAN, — отсюда place-before в примерах (номер подставьте под свой список правил или просто перетащите в WinBox). На споке они тоже нужны: rekey и DPD хаб может начать сам, и такие пакеты не всегда попадут под established,related.

hub — firewall для IPsec
# Споки приходят с любых адресов, поэтому без src-address
/ip firewall filter
add chain=input action=accept protocol=udp dst-port=500,4500 \
    in-interface-list=WAN comment="IPsec: IKE, NAT-T" place-before=0
add chain=input action=accept protocol=ipsec-esp \
    in-interface-list=WAN comment="IPsec: ESP" place-before=1
branch-a — firewall для IPsec
# Спок принимает IPsec только от хаба
/ip firewall filter
add chain=input action=accept protocol=udp dst-port=500,4500 \
    src-address=203.0.113.10 in-interface-list=WAN \
    comment="IPsec: IKE, NAT-T from hub" place-before=0
add chain=input action=accept protocol=ipsec-esp \
    src-address=203.0.113.10 in-interface-list=WAN \
    comment="IPsec: ESP from hub" place-before=1

Здесь и дальше 203.0.113.10 — внешний адрес хаба. Проверьте себя сразу: после первой попытки подключения у этих правил должны расти счётчики. Если у UDP 500 они растут, а у ESP и UDP 4500 стоят на нуле — смотрите, есть ли NAT между сторонами и не режет ли что-то выше по цепочке.

Проверка счётчиков
# Счётчики пакетов и байт у правил IPsec — на хабе и на споке
/ip firewall filter print stats where comment~"IPsec"

Схема

Ниже — сокращённая версия рабочей схемы: хаб на облачном CHR с белым адресом и филиалы на RB952 (PPPoE) и SXT LTE. Оба филиала за NAT провайдера, адреса у них динамические, поэтому туннель всегда поднимают споки, а хаб только отвечает.

Топология
       ┌──────────────────────────────┐
       │ hub (CHR, 203.0.113.10)      │
       │ AS 65000, 10.255.0.1         │
       │ xfrm-branch-a  xfrm-branch-b │
       └───────┬──────────────┬───────┘
      IKEv2/ESP│              │IKEv2/ESP
      (NAT-T)  │              │ (NAT-T)
┌──────────────┴───┐      ┌───┴──────────────┐
│ branch-a (PPPoE) │      │ branch-b (LTE)   │
│ AS 65101         │      │ AS 65102         │
│ 10.255.0.11      │      │ 10.255.0.12      │
│ LAN 192.168.10/24│      │ LAN 192.168.20/24│
└──────────────────┘      └──────────────────┘
  • 10.255.0.0/24 — транзитная сеть внутри туннелей. Адреса в ней одновременно служат IKE ID споков.
  • Каждому споку на хабе соответствует свой xfrm-интерфейс — это даёт отдельный BFD, отдельные счётчики и правила firewall на филиал.
  • Маршруты между площадками раздаёт eBGP: хаб в AS 65000, каждый спок в своей приватной AS.

Хаб: IKE, xfrm и шаблоны

Профиль и proposal — обычные. Proposal выбирается для каждого спока отдельно — в шаблоне его группы. На CHR без проблем работает aes-256-gcm, роутерам с аппаратным ускорением AES-CTR отдаём aes-256-ctr с SHA256, а для остальных оставляем aes-128-cbc. Какие алгоритмы ускоряет ваша модель, смотрите в таблице Hardware acceleration в документации MikroTik. Peer на хабе один, пассивный и без адреса: он принимает подключения от любого спока.

hub — IKE и proposal
/ip ipsec profile
add name=ike2 dh-group=modp2048 enc-algorithm=aes-256 hash-algorithm=sha256 \
    dpd-interval=2m dpd-maximum-failures=5

# Proposal под железо спока: GCM — CHR, CTR — аппаратный AES-CTR, CBC — остальные
/ip ipsec proposal
add name=esp-gcm auth-algorithms="" enc-algorithms=aes-256-gcm pfs-group=modp2048
add name=esp-ctr auth-algorithms=sha256 enc-algorithms=aes-256-ctr pfs-group=modp2048
add name=esp-cbc enc-algorithms=aes-128-cbc pfs-group=modp2048

# Один пассивный peer на всех споков: адрес не задан, хаб только отвечает
/ip ipsec peer
add name=branches exchange-mode=ike2 local-address=203.0.113.10 passive=yes \
    profile=ike2 send-initial-contact=no

Дальше — главное. На каждого спока создаём xfrm-интерфейс и группу политик с параметром interface=. В группу кладём шаблон без адресов — то есть 0.0.0.0/0 ↔ 0.0.0.0/0. В policy-based схеме такой шаблон перехватил бы весь трафик роутера, здесь же он действует только для пакетов, которые маршрутизация отправила в этот интерфейс.

Identity связывает спока с его группой. Поскольку споки приходят с непредсказуемых адресов из-за NAT, различаем их по remote-id. Внешний IP спока не важен — важен ID, который он предъявил при согласовании IKE.

hub — xfrm, группы, шаблоны, identity
/interface xfrm
add name=xfrm-branch-a mtu=1400
add name=xfrm-branch-b mtu=1400

/interface list
add name=ipsec
/interface list member
add interface=xfrm-branch-a list=ipsec
add interface=xfrm-branch-b list=ipsec

# Группа политик = привязка к xfrm-интерфейсу
/ip ipsec policy group
add name=branch-a interface=xfrm-branch-a
add name=branch-b interface=xfrm-branch-b

# Шаблоны без адресов: 0.0.0.0/0 <-> 0.0.0.0/0 внутри своего интерфейса
/ip ipsec policy
add group=branch-a proposal=esp-cbc template=yes comment=branch-a
add group=branch-b proposal=esp-cbc template=yes comment=branch-b

# Спок выбирается по IKE ID, а не по IP: споки за NAT и с динамикой
/ip ipsec identity
add peer=branches remote-id=address:10.255.0.11 secret="<PSK-branch-a>" \
    generate-policy=port-strict policy-template-group=branch-a comment=branch-a
add peer=branches remote-id=address:10.255.0.12 secret="<PSK-branch-b>" \
    generate-policy=port-strict policy-template-group=branch-b comment=branch-b

# Один адрес хаба на всех интерфейсах, point-to-point до каждого спока
/ip address
add address=10.255.0.1 network=10.255.0.11 interface=xfrm-branch-a
add address=10.255.0.1 network=10.255.0.12 interface=xfrm-branch-b

Обратите внимание на адресацию: на всех xfrm-интерфейсах хаба один и тот же адрес 10.255.0.1, но с network= конкретного спока. Получается набор point-to-point линков: маршрут до 10.255.0.11/32 смотрит в xfrm-branch-a, до 10.255.0.12/32 — в xfrm-branch-b.

Спок

На споке всё то же самое, но в одном экземпляре. Policy задаётся явно с peer=hub — спок инициирует подключение. Селектор снова 0.0.0.0/0 ↔ 0.0.0.0/0, но благодаря группе с interface=xfrm-hub шифруется только то, что маршрутизировано в туннель. Остальной трафик, включая сам IKE до хаба, идёт мимо.

Настройка спока идёт в два шага с перезагрузкой между ними. Сначала — всё, кроме policy: xfrm-интерфейс, адрес, peer, группа и identity.

branch-a, шаг 1 — xfrm, peer, identity
/interface xfrm
add name=xfrm-hub mtu=1400

/ip address
add address=10.255.0.11/24 interface=xfrm-hub

/ip ipsec profile
add name=ike2 dh-group=modp2048 enc-algorithm=aes-256 hash-algorithm=sha256 \
    dpd-interval=2m dpd-maximum-failures=5
# esp-ctr — если роутер ускоряет AES-CTR аппаратно, иначе esp-cbc
/ip ipsec proposal
add name=esp-ctr auth-algorithms=sha256 enc-algorithms=aes-256-ctr pfs-group=modp2048
add name=esp-cbc enc-algorithms=aes-128-cbc pfs-group=modp2048

/ip ipsec peer
add name=hub address=203.0.113.10/32 exchange-mode=ike2 profile=ike2

/ip ipsec policy group
add name=hub interface=xfrm-hub

# my-id должен совпадать с remote-id в identity на хабе
/ip ipsec identity
add peer=hub my-id=address:10.255.0.11 secret="<PSK-branch-a>" \
    generate-policy=port-strict policy-template-group=hub
branch-a — перезагрузка
# Выйти из safe mode (Ctrl+X), чтобы изменения сохранились, затем:
/system reboot
branch-a, шаг 2 — policy
# После перезагрузки — снова в safe mode (Ctrl+X)
# Дефолтный шаблон не нужен
/ip ipsec policy
set 0 disabled=yes
add peer=hub group=hub src-address=0.0.0.0/0 dst-address=0.0.0.0/0 \
    tunnel=yes proposal=esp-cbc

Второй филиал настраивается так же: 10.255.0.12, свой PSK, своя LAN. На хабе для него уже есть xfrm-branch-b и identity с remote-id=address:10.255.0.12.

Маршрутизация: BGP и BFD

Когда туннель — это интерфейс, статические маршруты на LAN других площадок уже работают: gateway=xfrm-hub. Но BGP удобнее: новый филиал появляется в таблицах всех площадок сам, а при падении туннеля маршрут уходит, и трафик может пойти по резервному пути.

На хабе один BGP connection слушает всю транзитную /24 — новых споков добавлять в BGP не нужно. BFD с интервалом 200 мс замечает обрыв туннеля за секунду, не дожидаясь hold-time и DPD.

hub — BGP и BFD
/routing id
add name=rid id=10.255.0.1

# Одинаковый фильтр на всех роутерах: пропускаем все частные диапазоны
/routing filter rule
add chain=bgp-local rule="if (dst in 10.0.0.0/8) { accept; }"
add chain=bgp-local rule="if (dst in 100.64.0.0/10) { accept; }"
add chain=bgp-local rule="if (dst in 172.16.0.0/12) { accept; }"
add chain=bgp-local rule="if (dst in 192.168.0.0/16) { accept; }"
add chain=bgp-local rule="reject;"

/routing bgp instance
add name=hub as=65000 router-id=rid

# Один connection на всю транзитную /24: споки подключаются сами
/routing bgp connection
add name=branches instance=hub local.address=10.255.0.1 local.role=ebgp \
    remote.address=10.255.0.0/24 hold-time=4m use-bfd=yes \
    output.filter-chain=bgp-local \
    output.redistribute=static,connected routing-table=main

/routing bfd configuration
add interfaces=xfrm-branch-a,xfrm-branch-b min-rx=200ms min-tx=200ms
branch-a — BGP и BFD
/routing id
add name=rid id=10.255.0.11

# Тот же фильтр, что на хабе, но сначала отбрасываем всё,
# что смотрит в WAN: LTE и DHCP провайдера выдают адреса из RFC-диапазонов
/routing filter rule
add chain=bgp-local comment="reject WAN" rule="if (gw-interface lte1) { reject; }"
add chain=bgp-local comment="reject WAN" rule="if (gw-interface ether1) { reject; }"
add chain=bgp-local rule="if (dst in 10.0.0.0/8) { accept; }"
add chain=bgp-local rule="if (dst in 100.64.0.0/10) { accept; }"
add chain=bgp-local rule="if (dst in 172.16.0.0/12) { accept; }"
add chain=bgp-local rule="if (dst in 192.168.0.0/16) { accept; }"
add chain=bgp-local rule="reject;"

/routing bgp instance
add name=hub as=65101 router-id=rid

/routing bgp connection
add name=hub instance=hub connect=yes local.role=ebgp \
    remote.address=10.255.0.1/32 hold-time=4m use-bfd=yes \
    input.filter=bgp-local output.filter-chain=bgp-local \
    output.redistribute=connected routing-table=main

/routing bfd configuration
add interfaces=xfrm-hub min-rx=200ms min-tx=200ms

Фильтрация туннелей, NAT и MTU

Расшифрованный трафик приходит с xfrm-интерфейса, поэтому его удобно фильтровать по interface list, как любой внутренний линк, — без матчеров ipsec-policy=in,ipsec, которые нужны в policy-based схеме.

hub — фильтрация трафика из туннелей
# Расшифрованный трафик приходит с xfrm-интерфейса —
# фильтруем его как обычный интерфейс, через список ipsec
/ip firewall filter
add chain=forward action=drop in-interface-list=ipsec dst-address=192.168.99.0/24 \
    comment="example: branches must not reach the management VLAN"

Исключения из NAT для IPsec больше не нужны: трафик до других площадок уходит в xfrm, а не в WAN, и под правило masquerade с out-interface-list=WAN не попадает. Стандартное правило defconf с ipsec-policy=out,none можно не трогать.

MTU xfrm-интерфейса мы выставили в 1400: это оставляет запас под ESP, NAT-T и PPPoE или LTE под ним. Чтобы TCP не упирался во фрагментацию, полезно поджать MSS на трафике через туннели:

MSS clamping
/ip firewall mangle
add chain=forward action=change-mss new-mss=clamp-to-pmtu protocol=tcp \
    tcp-flags=syn out-interface-list=ipsec passthrough=yes
add chain=forward action=change-mss new-mss=clamp-to-pmtu protocol=tcp \
    tcp-flags=syn in-interface-list=ipsec passthrough=yes

Проверка

Команды для проверки
# IKE-сессия поднята, видно IP спока после NAT
/ip ipsec active-peers print

# SA установлены, счётчики растут при трафике
/ip ipsec installed-sa print

# BFD и BGP через туннель
/routing bfd session print
/routing bgp session print

# Маршруты до LAN других площадок пришли по BGP через xfrm
/ip route print where bgp

# Связность LAN-LAN
/ping 192.168.20.1 src-address=192.168.10.1 count=5
/tool traceroute 192.168.20.1 src-address=192.168.10.1

Если в active-peers сессия есть, а installed-sa пустой — проверьте, что identity на хабе выбрала нужную группу: my-id спока и remote-id на хабе должны совпадать. Если SA есть, а BGP не поднимается — проверьте firewall input на хабе для трафика с interface list ipsec.

Что учесть

  • Только safe mode, и перезагрузка спока между созданием xfrm-группы и добавлением policy. Если доступ всё-таки пропал — сразу заходите через MAC-Winbox или консоль и отключайте policy.
  • Свой PSK для каждого спока. Identity выбирается по remote-id, который спок заявляет сам, — с общим ключом любой филиал может представиться другим. Для большого количества площадок лучше перейти на сертификаты.
  • Proposal без явного auth-algorithms использует SHA1. Если устройство тянет SHA256, AES-CTR или AEAD-шифры вроде GCM — задайте их явно, как в esp-ctr и esp-gcm.
  • Router ID задавайте явно через /routing id. Автовыбор «наименьший адрес» на роутере с LTE или PPPoE может взять динамический WAN-адрес, и ID поменяется при переподключении.
  • Одна BFD-конфигурация на интерфейс: дубли в /routing bfd configuration легко накопить при экспериментах.
  • Обе стороны должны быть на RouterOS 7.25 или новее. Другой стороной может быть и Linux со strongSwan и xfrm-интерфейсами — схема та же.

Итог

xfrm-интерфейсы убирают главную боль IPsec на MikroTik: туннель становится обычным линком. Подсети добавляются маршрутами, а не политиками; BGP и BFD работают прямо поверх ESP без GRE; firewall пишется по интерфейсам. Для hub-and-spoke с филиалами за NAT это сейчас самый чистый вариант на RouterOS.

Нужна такая же сеть между офисами?

Услуга «Сети»