До 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.
# Споки приходят с любых адресов, поэтому без 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# Спок принимает 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 на хабе один, пассивный и без адреса: он принимает подключения от любого спока.
/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.
/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.
/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# Выйти из safe mode (Ctrl+X), чтобы изменения сохранились, затем:
/system reboot# После перезагрузки — снова в 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.
/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/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 схеме.
# Расшифрованный трафик приходит с 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 на трафике через туннели:
/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.