Победа силы над разумом
Как мы поднимали GRE over IPsec между Juniper и Cisco, искали потерянные пакеты и случайно изобрели SELinux для маршрутизаторов
Есть Juniper vSRX на Junos 23.4R2-S5.5. Есть Cisco IOSv на IOS 15.7(3)M3. Между ними физическая сеть, два публичных loopback-адреса и совершенно невинное желание поднять GRE over IPsec с BGP поверх него.
Задача выглядела настолько стандартной, что даже подозрений не вызывала.
С одной стороны 185.37.100.200, с другой 200.200.70.2. Это одновременно endpoints для GRE и IKE/ESP. Под ними физические адреса 37.2.15.127 и 37.2.15.120. Внутри GRE адреса 10.255.255.1/30 и 10.255.255.2/30, поверх которых должен подняться eBGP между AS65001 и AS65002.
GRE на Juniper изначально должен был находиться в routing-instance gsm. Там же клиентские маршруты и BGP. Никакой экзотики.
IPsec решили делать route-based, через st0.0. На Cisco, соответственно, crypto map.
И вот тут началось увлекательное путешествие в недокументированные особенности человеческого страдания.
1. Всё работает, кроме передачи пакетов
Настраиваем IKEv2. AES-256-CBC, SHA-256, DH14, PSK. Для ESP аналогичный набор, PFS group14.
IKE поднимается.
IPsec SA устанавливаются.
SPI присутствуют, lifetimes отсчитываются, ESP decrypt увеличивается.
GRE показывает up/up.
BGP не поднимается.
Пинги не ходят.
Ну, стандартный набор признаков того, что всё прекрасно, кроме того, ради чего вообще затевалась эта конструкция.
Начинаем проверять маршруты, security policies, зоны, traffic selectors, MTU и конфигурацию обеих сторон.
Первое интересное открытие касается параметра Junos:
tunnel routing-instance destination
Название провоцирует мысль, что он задаёт VRF самого GRE. Но нет. Он задаёт таблицу маршрутизации, в которой Junos ищет внешний адрес назначения GRE.
Сам gr-0/0/0.0 при этом совершенно независимо включается в gsm:
routing-instances gsm interface gr-0/0/0.0
Пробуем разобраться с default, master, убрать параметр вообще. Junos любезно сообщает, что default и master здесь не подходят: параметр ожидает именованный routing-instance.
В какой-то момент даже пытаемся использовать gsm для поиска внешнего destination, после чего выясняем очевидное: в gsm нет маршрута до 200.200.70.2, да и быть ему там совершенно незачем.
GRE должен оставаться в gsm. Но где искать маршрут до его внешнего endpoint, ещё предстояло разобраться.
2. IPsec расшифровывает пакеты в никуда
Начинаем сравнивать счётчики.
И получаем замечательную картину:
ESP decrypted: 119
GRE input: 0
GRE output: 71
BGP: Connect
То есть Juniper получает ESP, успешно его расшифровывает, после чего GRE-пакеты куда-то бесследно исчезают.
Счётчики ошибок IPsec чистые. Authentication failures нет. Decryption failures нет. Replay errors тоже нет.
Смотрим GRE flow session:
Interface: st0.0
Self tunnel ID: 76
Self tunnel type: Generic
Pkts: 0
Индекс 76 совпадает с индексом нашего gr-0/0/0.0.
Это особенно прекрасно: Junos знает, какому GRE-интерфейсу соответствует поток, создал для него сессию, но передать туда хоть один пакет почему-то не может.
Делаем повторный замер.
ESP decrypted увеличивается со 119 до 141. Ещё 22 успешно расшифрованных пакета.
GRE RX как был нулём, так им и остался.
BGP всё ещё Connect.
Теперь уже понятно, что криптография в данном направлении работает. Проблема где-то между IPsec decapsulation и GRE processing.
3. Flow trace, или как Junos наконец начал разговаривать
Включаем security flow traceoptions, фильтруем трафик по публичным endpoints и GRE.
И наконец получаем не абстрактные счётчики, а совершенно конкретное признание:
'external-interface'(lo0.0) and
'routing-interface'(st0.0)
belong to different zones.
Re-route failed, pkt dropped.
Ага!
lo0.0 находится в untrust, st0.0 в vpn-gsm. При обработке туннеля Junos пытается выполнить reroute и упирается в несовпадение security zones.
Для проверки гипотезы временно переносим st0.0 в untrust.
Commit confirmed, повторный ping.
Десять пакетов. Ноль ответов.
Зато ошибка в flow trace меняется.
И вот тут начинается самое интересное.
flow_ipv4_rt_lkup_reroute_tunnel:
Re-routing NSP out-ifp from lo0.0 to st0.0
Packet loop detected in re-routing.
Returning error
Ну здравствуй, рекурсия.
Оказалось, мы построили конструкцию, в которой один и тот же публичный адрес 200.200.70.2 одновременно используется как IKE peer, GRE destination и IPsec traffic selector.
При этом в основной таблице маршрутизации существовал маршрут до удалённой сети через физического соседа:
200.200.70.0/24 → 37.2.15.120
Но IPsec создавал более специфичный маршрут к защищаемому endpoint через st0.0.
В результате GRE пытался отправиться через IPsec, а обработка IPsec при повторном lookup снова находила тот же st0.0.
Получалось примерно следующее:
«Чтобы отправить пакет, нужно зашифровать его».
«Чтобы зашифровать пакет, нужно отправить его».
«Куда?»
«В IPsec».
«А чтобы отправить в IPsec?»
«Нужно зашифровать».
И так до тех пор, пока flow engine не начинал подозревать, что его держат за идиота.
Причём смена security zone не исправила проблему, а лишь позволила пройти на одну стадию дальше и увидеть настоящую петлю.
Вот ради этого, собственно, и нужны flow traces, а не бесконечная перестановка permit any.
4. Разделение труда между routing instances
Решение оказалось архитектурным.
GRE остаётся там, где ему положено: в gsm. Там находятся 10.255.255.1/30, BGP и клиентская маршрутизация.
А st0.0 вместе с маршрутом до удалённого GRE endpoint переезжает в отдельный virtual-router ipsec-transport.
Внешний IKE/ESP при этом продолжает использовать обычную underlay-маршрутизацию через физический интерфейс.
Именно здесь пригодился тот самый параметр:
tunnel routing-instance destination ipsec-transport
Теперь GRE, находясь в gsm, ищет маршрут до своего внешнего destination в ipsec-transport, где видит:
200.200.70.2/32 → st0.0
А IPsec outer transport использует маршрут в основной inet.0:
200.200.70.0/24 → 37.2.15.120
То есть мы не переносили GRE из его VRF. Мы изолировали IPsec-подложку и её маршрутизацию от основного underlay.
И получили три независимых уровня:
gsm
GRE, 10.255.255.0/30, BGP
│
▼
ipsec-transport
st0.0, protected GRE route
│
▼
inet.0
IKE/ESP outer transport, physical underlay
При этом публичные endpoints на всех уровнях остались теми же самыми.
Красиво.
Теоретически.
Потому что на практике оно всё ещё не работало.
5. Juniper Power Mode, или привет из CentOS
Продолжаем копать dataplane.
На этом этапе уже понятно, что у нас было как минимум две разных проблемы: рекурсивная маршрутизация и потеря расшифрованных пакетов.
Чтобы отделить одну от другой, временно переносим IKE/ESP endpoints на физические адреса 37.2.15.127 и 37.2.15.120, оставляя GRE на публичных loopback.
Так мы исключаем конфликт одинаковых endpoints из эксперимента и можем отдельно проверить, что вообще происходит с пакетами после ESP decapsulation.
И снова получаем знакомую картину: SA работают, ESP decrypt увеличивается, а st0 не получает ожидаемый трафик.
Проверяем состояние flow engine.
И обнаруживаем:
Flow power mode: Enabled
Flow power mode IPsec: Enabled
Начинаем разбираться, что это за оптимизация и почему она вообще вмешивается в нашу жизнь.
В какой-то момент решаем провести простой A/B-тест.
set security flow power-mode-disable
Commit.
Повторяем проверку.
И пакеты внезапно начинают доходить.
st0 впервые получает входящий трафик. ICMP проходит. ESP decrypt и RX на интерфейсе начинают расти согласованно.
То есть после всех исследований flow sessions, маршрутов, зон, SPI, tunnel IDs и внутренних механизмов обработки пакетов выясняется, что Juniper в нашем конкретном случае ломает dataplane собственной оптимизацией.
Здесь я вспомнил старую добрую традицию установки CentOS.
Первое, что делалось после установки системы, это:
setenforce 0
И только потом уже начинали разбираться, почему что-нибудь не работает.
Видимо, теперь у этой традиции появилось сетевое продолжение:
set security flow power-mode-disable
CentOS умер, SELinux остался в наших сердцах, а Juniper решил, что такой прекрасный пользовательский опыт нельзя оставлять без наследника.
Разумеется, это не универсальная рекомендация отключать Power Mode на всех SRX. Но на нашем vSRX 23.4R2-S5.5 именно этот параметр оказался решающим для прохождения расшифрованного трафика.
6. Cisco: когда одного selector недостаточно
После победы над Juniper выясняется, что двустороннего счастья всё ещё нет.
Смотрим Cisco show crypto ipsec sa.
Один selector выглядит нормально, а рядом существует ещё один, GRE-specific, у которого нет полноценной рабочей SA. Растут send errors, outbound ESP encaps в нужном направлении не увеличивается.
Смотрим crypto ACL.
А там:
permit gre host 200.200.70.2 host 185.37.100.200
permit ip host 200.200.70.2 host 185.37.100.200
Две записи на одну пару адресов, одна из которых полностью покрывается другой.
Cisco умудрилась создать пересекающиеся selectors, после чего начала путаться в собственных SA.
Удаляем permit gre, оставляем только permit ip.
SA пересоздаются.
И наконец-то GRE начинает нормально работать в обе стороны.
Cisco пингует Juniper.
Juniper пингует Cisco.
BGP поднимается.
Тут уже можно было бы закончить, но оставалось вернуть IKE/ESP на публичные loopback-адреса, потому что временная диагностическая схема с физическими endpoints не соответствовала исходному требованию.
7. Финальная сборка
Возвращаем IKEv2 на 185.37.100.200 ↔ 200.200.70.2.
На Cisco обновляем keyring peer, remote identity, local identity, crypto map peer и local-address.
На Juniper возвращаем gateway address и local-address, сохраняя external-interface ge-0/0/0.0.
GRE остаётся в gsm, st0 остаётся в ipsec-transport, внешний IPsec использует underlay.
Проверяем.
Cisco:
IKEv2: READY
Local: 200.200.70.2/500
Remote: 185.37.100.200/500
ESP encaps: 25
ESP decaps: 25
Send errors: 0
Recv errors: 0
Juniper:
IKEv2: UP
IPsec tunnels: 1
GRE: up/up
BGP: Established
SPI на обоих устройствах совпадают в соответствующих направлениях.
Пинги 5/5 с обеих сторон.
Juniper получает один BGP-префикс от AS65002.
И главное, требование одинаковых публичных endpoints для GRE и IKE/ESP выполнено не только в конфигурации, но и подтверждено реальным трафиком.
После нескольких часов копания в недрах Junos и IOSv эта конструкция наконец работает.
Даже немного неожиданно.
Рецепт починки
Для тех, кому неинтересно повторять весь этот цирк самостоятельно.
1. Правильно разделить маршрутизацию на Juniper
GRE остаётся в пользовательском routing-instance gsm.
set routing-instances gsm instance-type virtual-router
set routing-instances gsm interface gr-0/0/0.0
Создаём отдельный routing-instance для IPsec-подложки:
set routing-instances ipsec-transport instance-type virtual-router
set routing-instances ipsec-transport interface st0.0
set routing-instances ipsec-transport routing-options static route 200.200.70.2/32 next-hop st0.0
Настраиваем GRE:
set interfaces gr-0/0/0 unit 0 tunnel source 185.37.100.200
set interfaces gr-0/0/0 unit 0 tunnel destination 200.200.70.2
set interfaces gr-0/0/0 unit 0 tunnel routing-instance destination ipsec-transport
set interfaces gr-0/0/0 unit 0 family inet mtu 1400
set interfaces gr-0/0/0 unit 0 family inet address 10.255.255.1/30
В основной таблице оставляем физический маршрут до удалённого endpoint:
set routing-options static route 200.200.70.0/24 next-hop 37.2.15.120
Принципиально важно: внешний IKE/ESP должен выходить через underlay, а GRE должен попадать на st0 через отдельную таблицу.
2. Настроить route-based IPsec
Ключевые параметры Juniper:
set security ike gateway GSM-CISCO address 200.200.70.2
set security ike gateway GSM-CISCO local-address 185.37.100.200
set security ike gateway GSM-CISCO external-interface ge-0/0/0.0
set security ike gateway GSM-CISCO version v2-only
set security ipsec vpn GSM-VPN bind-interface st0.0
set security ipsec vpn GSM-VPN ike gateway GSM-CISCO
set security ipsec vpn GSM-VPN ike ipsec-policy GSM-IPSEC-POL
set security ipsec vpn GSM-VPN traffic-selector GRE-TS local-ip 185.37.100.200/32
set security ipsec vpn GSM-VPN traffic-selector GRE-TS remote-ip 200.200.70.2/32
IKE/IPsec proposals и policies должны быть согласованы с Cisco: AES-256-CBC, SHA-256, DH14, PFS group14.
3. Отключить проблемный Power Mode
На нашем vSRX:
set security flow power-mode-disable
После commit проверить:
run show security flow status
Оба Power Mode должны быть Disabled.
Это исправление подтверждено именно нашим экспериментом. Для другой платформы или версии Junos сначала следует проверить применимость.
4. Не создавать пересекающиеся crypto selectors на Cisco
Правильная ACL:
ip access-list extended GSM-GRE
permit ip host 200.200.70.2 host 185.37.100.200
Без дополнительного permit gre на ту же пару адресов.
Crypto map:
crypto map GSM-MAP local-address Loopback0
crypto map GSM-MAP 10 ipsec-isakmp
set peer 185.37.100.200
set transform-set GSM-ESP
set pfs group14
set ikev2-profile GSM-IKE-PROFILE
match address GSM-GRE
На физическом интерфейсе:
interface GigabitEthernet0/0
crypto map GSM-MAP
И underlay-маршрут:
ip route 185.37.100.0 255.255.255.0 37.2.15.127
5. Проверить результат, а не просто наличие SA
На Juniper:
run show security ike security-associations
run show security ipsec security-associations
run show security ipsec statistics
run show interfaces gr-0/0/0.0 statistics
run show bgp summary
run ping 10.255.255.2 routing-instance gsm source 10.255.255.1 count 5
На Cisco:
show crypto ikev2 sa
show crypto ipsec sa
show ip route vrf gsm
ping vrf gsm 10.255.255.1 source 10.255.255.2 repeat 5
Нужны не просто READY, UP и ACTIVE, а реальные двусторонние счётчики ESP, входящие пакеты GRE и BGP Established.
Если ESP decrypt растёт, а GRE RX остаётся нулевым, ищем проблему между IPsec decapsulation и GRE processing.
Если flow trace пишет Packet loop detected in re-routing, проверяем таблицу поиска внешнего GRE destination и рекурсию через st0.
Если на Cisco есть SA без SPI, send errors или несовпадающие selectors, проверяем crypto ACL.
Если всё работает только в одну сторону, сравниваем SPI и дельты encaps/decaps на обоих устройствах, а не пытаемся лечить BGP.
И уже после этого можно думать про MTU, PMTUD, rekey и ужесточение security policies.
Вместо заключения
Мы начали с простой задачи поднять GRE over IPsec.
По дороге выяснили, что Junos может расшифровывать ESP и не доставлять пакеты, что один и тот же адрес способен устроить рекурсию между GRE и IPsec, что security zones могут скрывать следующую ошибку, а Cisco умеет создавать себе проблемы двумя пересекающимися строками ACL.
В итоге победили три вещи: рекурсивную маршрутизацию, проблемный Power Mode и пересекающиеся selectors.
Потратили на это несколько часов, изучили некоторое количество тайных знаний и получили вполне работоспособную схему.
Победа силы над разумом состоялась.
Разум предлагал верить статусам UP и READY.
Сила заставила нас смотреть, куда реально деваются пакеты.
И, как обычно, оказалась права.