Почему трафик филиалов тормозит через ЦОД и как исправить это с помощью Identity‑First

Привет, Хабр! Меня зовут Андрей Бирюков. Я — независимый эксперт в области ИТ и ИБ, преподаю в учебных центрах и пишу статьи и книги.
При построении сетей архитекторы привыкли придерживаться принципа построения иерархии Access → Distribution → Core → Firewall → Internet. Однако в современных сетях эта иерархия превращается в сетку, где точка принятия решения о маршрутизации это уже не IP‑адрес получателя, а идентификатор (SID) пользователя.
В этой статье мы пройдем путь от того «как было» к тому «как стало» на конкретных конфигах и логике принятия решений.
Проблемы обратных маршрутов
Давайте рассмотрим один достаточно типовой сценарий. У компании имеется основной офис в Москве, в котором располагается корневой файрволл, обеспечивающий выход в интернет. Также есть офис в Екатеринбурге.
Что происходит, когда пользователь этого офиса обращается к внешним ресурсам в облаке.
Путь трафика (классический подход):

Задержка: 35 мс (Ekb‑Msk) * 2 (туда и обратно) + время обработки firewall = ~80 мс. Это терпимо. Но когда вы включаете SSL Decryption на фаерволе, задержка превращается в 150 мс, и приложение в облаке начинает «дергаться».
Теперь рассмотрим те проблемы, которые вызывает подобный подход.
Здесь первой ошибкой сетевого администратора является попытка разрешить доступ через PBR (Policy‑Based Routing), чтобы направить облачный трафик напрямую в интернет, минуя фаервол. Для этого архитектор готовит следующий access‑list:
ip access-list extended BYPASS_MSFT
permit tcp any 13.107.0.0/16
permit tcp any 52.112.0.0/14
!
route-map PBR_TO_INTERNET permit 10
match ip address BYPASS_MSFT
set ip next-hop 172.16.255.1 (локальный интернет-шлюз)Но в итоге мы получаем довольно странную картину, так как ответ из облака приходит в Екатеринбург напрямую, а следующий запрос от пользователя уходит через Москву.
При этом, таблица состояний на фаерволе сбивается, потому что он видит только половину сессии.
В результате сетевики вынуждены настраивать «симметричный возврат» (Unicast Reverse Path Forwarding в loosen mode), что приводит к появлению дополнительных дыр в безопасности.
Так что предложенный вариант нельзя назвать самым лучшим, поэтому давайте посмотрим на другие решения.
Модель «Identity‑First»
Для начала, давайте зададимся одним простым вопросом, а всегда ли нужно туннелировать пользователя в ЦОД?
На самом деле нам достаточно туннелировать только политики в Edge.
Например, мы можем вместо L3VPN использовать SD‑WAN Fabric с интеграцией с ZTNA (Zero Trust Network Access). Но ключевая магия у нас может происходить не в маршрутизаторе, а в DNS Forwarder и агентe на рабочей станции.
Теперь логика маршрутизации существенно изменится, так как агент на ноутбуке будет поднимать туннель (WireGuard/IPsec) не до Москвы, а до ближайшей точки присутствия (PoP) провайдера (например, в Казани).

В этой PoP стоит виртуальный маршрутизатор, который смотрит не на dst‑ip, а на Certificate SAN пользователя и полученный из AD тэг приложения.
Соответственно, если приложение помечено как SaaS‑FastPath, трафик отпускается в интернет прямо из Казани.
Если же приложение помечено как Corporate‑Critical (например, это SAP или запрос к внутренней базе), трафик уходит по IPsec Tunnel в ЦОД, но не в фаервол, а напрямую на Service Mesh приложения.
В результате, мы не создаем дыр в безопасности и при этом не сильно снижаем скорость прохождения трафика.
Давайте в качестве примера рассмотрим реализацию на базе Linux‑хоста в качестве PoP (маршрутизатор на Free Range Routing с использованием nftables. Здесь вместо настройки статических маршрутов мы используем Policy Routing на основе cgroups, который ставится агентом на клиенте.
Но на стороне PoP мы встречаем трафик и обрабатываем его на уровне политик.
Вот минимальный фрагмент логики на стороне шлюза PoP (связка Python + pyroute2), который эмулирует принятие решения:
from pyroute2 import IPRoute
import subprocess
# Получаем маркер пользователя из пакета (в реальности - из TLS SNI или Cookie)
# Допустим, мы инкапсулировали Identity в поле DSCP (неправильно, но для примера)
# Или используем VXLAN GPE с метаданными.
def classify_traffic(iface="eth0"):
ip = IPRoute()
# Создаем отдельную таблицу маршрутизации для "SaaS" трафика
# Таблица 100 - быстрый путь без инспекции
ip.route("add", dst="0.0.0.0/0", gateway="100.64.0.1", table=100) # Прямой выход в интернет
# Таблица 200 - путь через фаервол-прокси в ЦОД
ip.route("add", dst="10.0.0.0/8", gateway="192.168.255.2", table=200) # IPsec к Core
# Правило выбора таблицы: если пакет имеет fwmark 0x01 (установленный агентом)
ip.rule("add", fwmark=0x01, table=100)
ip.rule("add", fwmark=0x02, table=200)
# Включаем маршрутизацию источника (чтобы обратный трафик шел через тот же PoP)
# echo 1 > /proc/sys/net/ipv4/conf/all/rp_filter
# Устанавливаем значение 2 (Loose Mode), но только для трафика с марком, чтобы не нарушить безопасность.
subprocess.run(["sysctl", "-w", "net.ipv4.conf.all.rp_filter=2"])
if name == "__main__":
classify_traffic()В итоге мы можем сформировать разные маршрутные таблицы для различных видов трафика.
DNS как маршрутизатор
А теперь рассмотрим наиболее интересный и важный шаг в настройке нашего сетевого взаимодействия.
В предлагаемой архитектуре вы перестаете полагаться на маршруты в BGP, а начинаете управляеть трафиком через DNS‑ответы.
То есть, вместо того чтобы раздавать по DHCP один DNS‑сервер (например, московский DC), вы даете филиалам локальный кеширующий резолвер (CoreDNS или dnsmasq), который подключен к Central Policy Controller.
Вот пример Corefile для CoreDNS (PoP в Казани). Мы используем плагин view для разделения клиентов по подсетям, но главное — мы используем плагин rewrite, чтобы перенаправлять запросы к внутренним сервисам на локальный IP балансировщика.
.:53 {
# Сначала проверяем, не является ли запрос внутренним
view internal {
expr type() == 'A' && name() matches '\.internal\.company\.$'
# Перенаправляем запрос к сервису "hr" на локальный прокси в этом PoP
rewrite name hr.internal.company. hr-proxy.pop-kazan.svc.cluster.local
forward . 10.200.0.1 # Внутренний кластерный DNS
}
view external {
# Все остальные запросы (SaaS)
# Мы не даем реальный IP Microsoft. Мы даем IP ближайшего кеширующего прокси (Squid + SSL Bump)
# Или, если политика разрешает, отдаем реальные публичные IP через резолвер Cloudflare (1.1.1.1)
forward . 1.1.1.1 1.0.0.1 {
prefer_udp
max_concurrent 1000
}
# Логируем все DNS-запросы для Security Analytics (угрозы через DNS-туннели)
log
}
}Здесь логика работы DNS заключается в том, что, если пользователь идет на google.com, DNS отдает ему CNAME на локальный кеш в Казани. Трафик даже не поднимается в IP‑стек маршрутизации до Москвы.
Сохранение состояния при отказе PoP
Однако, тут есть важная проблема, связанная с тем, что произойдет, когда PoP в Казани отвалится? Клиент потеряет связь с маршрутизатором, и все его TCP сессии оборвутся.
Это неприемлемо например, для RDP‑соединений.
В качестве решения здесь можно воспользоваться Multipath TCP (MTCP) на уровне агента и Anycast IP для PoP. MTCP представляет собой расширение протокола TCP, которое позволяет одновременно использовать несколько сетевых путей для передачи данных
Мы поднимаем один и тот же VIP (например, 10.255.255.254) на всех PoP‑точках в России и анонсируем его в BGP с разным весом (Local Pref).
Но чтобы не было флопа (хаотичного изменения маршрута) при сбое, мы используем BGP PIC (Prefix Independent Convergence) с таймером удержания.
Конфиг BGP на маршрутизаторе PoP (модуль GoBGP) может иметь следующий вид:
bgp:
router-id: "10.0.1.1"
as: 65001
neighbors:
- neighbor-address: "10.0.0.2" # Соседний PoP для резерва
peer-as: 65001
route-reflector-client: true
afi-safi:
- "ipv4-unicast"
policies:
# Анонсируем наш локальный префикс для балансировки
- name: "announce-anycast"
type: "accept"
statement:
- condition:
prefix: "10.255.255.254/32"
action:
route-action: "accept"
set-local-pref: 150 # В основном PoP вышеПри этом, на клиенте под Linux мы настраиваем WireGuard с двумя эндпоинтами и скриптом переключения:
# /etc/wireguard/wg0.conf
[Interface]
PrivateKey = xxxx
Address = 10.1.0.2/32
# Таймеры для быстрого переключения
Table = auto
FwMark = 0x01
[Peer]
PublicKey = PoP_Kazan
Endpoint = 10.255.255.254:51820 # Anycast IP!
PersistentKeepalive = 15
AllowedIPs = 0.0.0.0/0
[Peer]
PublicKey = PoP_Moscow
Endpoint = 10.255.255.254:51820 # Тот же IP, другой физический хост
PersistentKeepalive = 25
AllowedIPs = 0.0.0.0/0В итоге, когда пакет идет на 10.255.255.254, маршрутизатор ядра выбирает ближайший по метрике BGP PoP, и если он падает, BGP убирает маршрут, и WireGuard автоматически начинает стучаться в следующий (поскольку IP один, но MAC‑адрес соседнего маршрутизатора уже другой).
Здесь единственная проблема это обновление ARP на клиенте, поэтому мы используем NDP (для IPv6) или ставим arp_ignore=2 и arp_announce=2, чтобы клиент постоянно перезапрашивал MAC‑адрес для Anycast IP.
Инспекция SSL без деградации
Самой большой проблемой в старой модели является проведение инспекции трафика, поэтому в новой модели мы выносим ее в Edge, но делаем выборочной.
Для этого, в PoP мы запускаем Envoy Proxy как Forward Proxy, который фильтрует трафик по SNI.
В результате для .banking.ru мы включаем полную инспекцию, а для.rutube.ru пропускаем «как есть», только проверяем сертификат на валидность (не отозван ли через OCSP).
Конфигурация Envoy в формате yaml для маршрутизации на уровне L7 внутри PoP будет следующей:
static_resources:
listeners:
- name: proxy_listener
address:
socket_address:
address: 0.0.0.0
port_value: 10000
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
codec_type: AUTO
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: backend
domains: ["*"]
routes:
# Маршрут для критичных приложений
- match:
safe_regex:
google_re2: {}
regex: '.*\.(sap|oracle)\.internal$'
route:
cluster: backhaul_to_dc
# Включаем таймаут для длинных операций
timeout: 300s
# Маршрут по умолчанию (SaaS)
- match:
prefix: "/"
route:
cluster: direct_egress
# Используем HTTP/2 для улучшения работы с Microsoft
http2_protocol_options: {}
http_filters:
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
clusters:
- name: backhaul_to_dc
connect_timeout: 30s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: backhaul_to_dc
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 10.10.10.1 # IP шлюза в ЦОД
port_value: 443
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
# Используем mTLS для безопасности
common_tls_context:
tls_certificates:
- certificate_chain: { filename: "/certs/client.crt" }
private_key: { filename: "/certs/client.key" }
- name: direct_egress
connect_timeout: 10s
type: LOGICAL_DNS
dns_lookup_family: V4_ONLY
lb_policy: MAGLEV # Используем консистентный хеш для кеширования
load_assignment:
cluster_name: direct_egress
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 8.8.8.8 # Условный шлюз (реально - локальный NAT)
port_value: 443Подведем итог
Старая архитектура что называется тянула пользователя к данным, работая по принципу User → DC → Firewall → Internet.
Новая тянет данные к пользователю и политику к данным.
Агент на ноутбуке получает от контроллера список доменов для прямого доступа и список для туннелирования.
В свою очередь, для прямого доступа ноутбук шлет запрос на локальный PoP, где стоит Envoy с прозрачным кешем (если это статика вроде JS‑библиотек) или просто проксирует в 1.1.1.1.
Для туннелирования трафик заворачивается в сегмент VXLAN с тэгом пользователя (User‑ID) и отправляется в ЦОД.
Фаервол в ЦОД не проверяет IP пакета, вместо этого он проверяет VXLAN‑тэг и применяет политику микросегментации (например, пользователь из отдела кадров не может пинговать БД, даже если они в одной подсети).
Соответственно, управление трафиком в филиалах сводится к одному правилу IPtables: ip route add default via wg0 table 100.
Маршруты физических интерфейсов используются только для управления (SSH) и DHCP.
Но здесь важно понимать, что при таком подходе вы полностью убиваете классический мониторинг через ICMP (ping).
То есть, вы не сможете пропинговать конечный сервер из ЦОД, потому что маршрут зависит от идентификатора сессии.
Мониторинг переводится на Active Probing через синтетические транзакции (например, искусственный пользователь логинится в SAP раз в минуту).
Это ломает голову сетевым инженерам, привыкшим к MTR, но это необходимая плата за архитектуру Zero‑Trust.

Когда маршрутизация, инспекция и отказоустойчивость начинают зависеть от контекста сессии, привычных сетевых инструментов уже недостаточно. Сначала нужно научиться видеть и контролировать сам трафик, а затем — понимать, что происходит с ним в production.
Это помогает перейти от точечной настройки маршрутов к управляемой и наблюдаемой сетевой архитектуре, где поведение системы можно объяснить, проверить и изменить осознанно.
Продолжить тему можно на бесплатных открытых уроках OTUS:
22 сентября в 20:00. «Автоматизация управления трафиком с mitmproxy». Записаться
23 сентября в 20:00. «eBPF: рентгеновское зрение для production». Записаться
А полный список бесплатных уроков сентября можно посмотреть в дайджесте.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.