Инциденты с prefix hijacking случаются довольно часто, когда трафик массово отправляется не тем путем, которым должен. Некоторые из них, может, и связаны со злым умыслом, но большинство случайны и происходят по вине невнимательных админов. В этой статье расскажу, как интернет-провайдеры могут предотвратить их или свести к минимуму, а заодно и как самому не стать их виновником.
В примерах настроек я использую стек протоколов FreeRangeRouting. Пользователям BIRD или OpenBGPD придется действовать по аналогии.
В этой статье предполагается, что у тебя уже есть опыт работы с FRR, Quagga или Cisco IOS и базовые знания о Border Gateway Protocol (BGP).
Суть проблемы
Протокол маршрутизации BGP сам по себе не содержит никакой информации о том, кому принадлежит та или иная сеть. Анонсировать чужие префиксы при этом вполне обычная практика — небольшие сети анонсируют свои сети транзитным провайдерам с развитой сетью, а те уже анонсируют их всем остальным. Кроме того, на точках обмена трафиком активно используются так называемые сервера маршрутов (route servers), которые позволяют получать маршруты ко всем сетям участников точки сразу, без настройки отдельной сессии с каждым из них.
BGP намеренно спроектирован так, чтобы можно было фильтровать и модифицировать маршруты произвольным образом без нарушения работы самого протокола. OSPF, к примеру, принципиально не позволяет даже фильтровать входящие маршруты: поскольку каждый маршрутизатор OSPF строит и поддерживает полную карту соединений в сети, отказ одного маршрутизатора запоминать часть сети может нарушить ее связь со всеми остальными. Протоколы с отслеживанием состояния каналов (link-state) вроде OSPF прекрасно подходят для внутренней маршрутизации, где на выбор пути влияют только технические соображения: длина пути и его пропускная способность.
Поскольку интернет — объединение независимых сетей, в силу вступают экономические и политические факторы. Самый короткий путь не всегда самый выгодный. Из-за этого реализации BGP вынужденно позволяют такие виды фильтрации и модификации анонсов, которые неприемлемы в других протоколах.
Отсутствие аутентификации открывает большой простор для случайных ошибок и намеренных злоупотреблений. Давай разбираться, как их избежать.
Фильтрация исходящих анонсов
Прежде чем рассматривать механизмы защиты от чужих ошибок, давай рассмотрим, как не стать виновником инцидента самому.
Случайная утечка одного префикса встречается крайне редко, в большинстве случаев утекает вся таблица маршрутов. Эта ситуация весьма неприятна для всех участников. Инициатор ненамеренно становится транзитным провайдером для жертв и берет на себя расходы по передаче их трафика. Если сеть не готова к возросшей нагрузке, такой инцидент способен положить и сеть инициатора, и сети жертв.
Неопытные админы, которые до этого работали с единственным подключением к провайдеру, часто забывают про фильтры.
Предположим, есть вот такой конфиг:
1 2 3 4 5 |
router bgp 64496 network 192.0.2.0/24 neighbor 203.0.113.10 remote-as 64500 neighbor 203.0.113.10 description AwfulTransit |
Диапазон номеров автономных систем 64496–64511 зарезервирован для примеров и документации в RFC5398. Он не совпадает с диапазоном для частного использования (64512–65534). Разница такая же, как между диапазоном адресов IPv4 для частного использования из RFC1918 и сетями 192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24 (TEST-NET-[123]).
Сети и номера AS из частных диапазонов выделены для того, чтобы внутренние ресурсы организаций не конфликтовали в публичном интернете, а зарезервированные для документации — чтобы бездумное применение примеров из статей не конфликтовало с реальными сетями.
Мы анонсируем провайдеру сеть 192.0.2.0/24. Провайдер анонсирует нам все маршруты интернета. В этой ситуации никакие фильтры не нужны, потому что ни один протокол маршрутизации не анонсирует полученные от соседа маршруты обратно ему же. Это базовый механизм предотвращения петель маршрутизации.
В то же время все протоколы, включая BGP, анонсируют все маршруты всем остальным соседям, если не указано обратное. Чем больше связность сети и число альтернативных маршрутов к каждой сети, тем она надежнее. Если, конечно, все участники могут и согласны пропускать через себя этот трафик — а клиенты провайдеров не готовы и не согласны.
Предположим, что ты добавил подключение ко второму провайдеру:
1 2 |
router bgp 64496 neighbor 203.0.113.20 remote-as 64510 |
С такими настройками твой маршрутизатор начнет анонсировать маршруты одного провайдера другому.
Чтобы этого не произошло, нужно явно указать, что анонсировать.
Если у тебя всего один маршрутизатор BGP и все маршруты приходят из какого-то внутреннего протокола или опций network, проще всего отфильтровать по пустому AS path. Номер автономной системы добавляется в путь маршрута не в момент его зарождения, а в момент анонса, поэтому у локально порожденных маршрутов он пустой. Пустой строке соответствует регулярное выражение ^$.
1 2 3 4 5 |
bgp as-path access-list LocalOnly permit ^$ ! route-map LocalOnly permit 10 match as-path LocalOnly ! |
Если у тебя есть свои клиенты или маршруты по иной причине приходят опять же из BGP, то придется фильтровать явно, с помощью prefix-list.
На практике в случае с клиентом и провайдером при утечке таблицы провайдер автоматически отключит сессию клиента или проигнорирует лишние маршруты. Успешный захват префиксов обычно происходит между равноправными сетями: транзитными провайдерами или участниками точки обмена трафиков.
Как провайдеры это предотвращают? Самая простая и неспецифичная мера защиты — опция maximum-prefix.
Опция maximum-prefix
Эта опция делает ровно то, о чем говорит ее название. Если количество сетей в анонсе соседа превышает указанное значение, сессия автоматически обрывается и соседу отправляется BGP notification.
В FRR это делается вот так:
1 2 3 |
router bgp 64500 address-family ipv4 unicast neighbor 192.0.2.10 maximum-prefix 1 |
Очевидный недостаток: нарушение политики со стороны клиента обрывает всю связь с провайдером. Добавление новой сети или разделение существующей на части становится для клиента весьма рискованной операцией, для которой нужно уведомить провайдера и дождаться его подтверждения перед тем, как менять настройки.
Альтернативы
Увы, все альтернативы в рамках самого BGP — либо немасштабируемые, либо чисто эвристические.
Очевидный вариант, подходящий для провайдеров и клиентов — явный список разрешенных префиксов. В этом случае клиенту также нужно уведомлять провайдера каждый раз при появлении новой сети, но новые префиксы не обрушат внезапно всю сессию — и можно разрешить не точное совпадение, а сеть со всеми ее подсетями.
Например, разрешим принимать от клиента сети 192.0.2.0/24 и 203.0.113.0/24 вместе со всеми их подсетями вплоть до /32:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
! ip prefix-list MyPrefixes seq 10 permit 192.0.2.0/24 le 32 ip prefix-list MyPrefixes seq 20 permit 203.0.113.0/24 le 32 ! route-map Transit-Out permit 10 match ip address prefix-list MyPrefixes ! router bgp 64500 neighbor 198.51.100.1 remote-as 64501 ! address-family ipv4 unicast neighbor 198.51.100.1 route-map Transit-Out out exit-address-family ! |
Другой вариант — фильтрация по максимальной длине AS path. По аналогии с пустым путем локальных маршрутов из фильтра для исходящего направления мы можем разрешить на стороне провайдера все маршруты, у которых путь состоит из ровно одной AS, — с помощью регулярного выражения ^([0-9]+)(_\1)*$. Это автоматически исключит маршруты, которые пришли к нашему соседу извне, а не были порождены им самим. Очевидно, это защитит только от случайных ошибок, а не намеренных попыток анонсировать чужую сеть.
Почему не [0-9]+? Многие люди для контроля за тем, через какого провайдера к ним пойдет входящий трафик, используют опцию AS path prepend, которая искусственно делает путь длиннее, чем он есть, поскольку добавляет в него локальную автономную систему несколько раз. Выражение, которое соответствует пути из ровно одного номера AS, запретило бы такие анонсы.
Ни один из этих вариантов не подходит для пиринга между провайдерами или участниками точек обмена трафиком. Знать содержимое анонсов наперед в этих случаях невозможно, нужно проверять именно аутентичность анонсов.
Сам BGP используется еще с девяностых, но механизм для проверки аутентичности анонсов появился недавно и называется Resource Public Key Infrastructure (RPKI).
RPKI
RPKI — сравнительно новый механизм, который позволяет автоматически проверять принадлежность префиксов к автономным системам.
RPKI не является частью BGP и не ограничивается им. По сути, это эквивалент whois с цифровыми подписями и набором протоколов для их автоматической проверки. Подписывать сами анонсы было бы бессмысленно, поскольку их модификация при передаче неизбежна — каждый маршрутизатор должен добавить в AS path свой номер автономной системы. Вместо этого BGP origin validation проверяет соответствие адреса сети и автономной системы источника — крайнего правого номера в AS path.
К сожалению, RPKI — не настоящее, а будущее. Еще далеко не все операторы автономных систем участвуют в ней, несмотря на очевидные преимущества, поэтому просто отфильтровать все анонсы, для которых нет данных валидации, не выйдет. Можно только отфильтровать явно не соответствующие автономной системе источника. Тем не менее ознакомиться с ней и увидеть ее в работе сейчас может каждый.
Хранение базы данных соответствия сетей и автономных систем и проверка подписей производятся не на самом маршрутизаторе, а на отдельном сервере — для экономии ресурсов.
Запускаем сервер RPKI
Для демонстрации потребуется хост для сервера RPKI с любой UNIX-подобной системой, программы rsync и JDK8+ и два хоста с установленным FRR.
Прежде чем настраивать что-то в FRR, нужно запустить сам сервер RPKI. Мы будем использовать реализацию RIPE, которая состоит из двух компонентов: rpki-validator-3 и rpki-rtr-server.
Запускаем rpki-validator
Сначала нужно запустить rpki-validator-3. Он доступен в виде образа для Docker и пакетов RPM для CentOS 7, а также в виде обычного tgz-архива с исполняемым файлом, который запускается на любой системе.
1 2 3 4 |
$ wget https://ftp.ripe.net/tools/rpki/validator3/prod/generic/rpki-validator-3-latest-dist.tar.gz $ tar xfz ./rpki-validator-3-latest-dist.tar.gz $ cd rpki-validator-<номер версии> $ ./rpki-validator-3.sh |
Настройки для баз данных всех RIR (RIPE, ARIN и других) уже включены в комплект, он автоматически загрузит их базы данных. Для его работы требуются только JDK8 или новее и rsync. Объем скачиваемых данных пока в пределах гигабайта.
Если ты планируешь запускать оба сервиса на одной машине, дополнительная настройка не требуется. Если нет, нужно поправить опцию server.address в conf/application.properties: по умолчанию он слушает только на localhost.
Запускаем rpki-rtr-server
Сам протокол RTR реализует другой проект: rtr-server. Для своей работы он требует запущенного rpki-validator, поэтому предыдущий шаг пропустить нельзя. Если он не сможет подключиться к валидатору, он сообщит об этом исключением вида I/O error on GET request for "http://localhost:8080/api/objects/validated": Connection refused (Connection refused).
Процедура его запуска столь же проста:
1 2 3 4 |
$ wget https://ftp.ripe.net/tools/rpki/validator3/prod/generic/rpki-rtr-server-latest-dist.tar.gz $ tar xfz ./rpki-rtr-server-latest-dist.tar.gz $ cd rpki-rtr-server-<номер версии> $ ./rpki-rtr-server.sh |
Он тоже по умолчанию слушает только на localhost. Если настраивать FRR с проверкой аутентичности анонсов ты будешь на другой машине, нужно поправить опцию rtr.server.address в conf/application.properties.
После того как оба сервиса успешно запустятся, можно настроить сам FRR.
Настраиваем FRR
Клиент RPKI реализован в библиотеке rtrlib. RTR здесь — RPKI to Router protocol, протокол обмена данными между сервером RPKI и маршрутизатором. Большинство свободных реализаций BGP, включая FRR, используют именно ее.
По умолчанию RPKI в BGPd выключен, и чтобы его включить, нужно поправить /etc/frr/daemons:
1 |
bgpd_options=" --daemon -A 127.0.0.1 -M rpki" |
Сначала настроим маршрутизатор условного провайдера. Предположим, что rtr-server запущен на 192.168.56.1. Зайдем в vtysh и добавим следующие настройки:
1 2 3 4 5 |
rpki rpki polling_period 1000 rpki timeout 10 rpki initial-synchronisation-timeout 30 rpki cache 192.168.56.1 8323 preference 1 |
Проверить статус соединения можно командой show rpki cache-connection. Если все прошло нормально, ее вывод будет выглядеть так:
1 2 |
Connected to group 1 rpki tcp cache 192.168.56.1 8323 pref 1 |
Теперь настроим сам BGP. В route-map для не соответствующих автономной системе префиксов мы понизим local preference до 5 (ее значение по умолчанию — 100), чтобы эффект можно было увидеть в show ip bgp:
1 2 3 4 5 6 7 8 9 10 |
router bgp 64496 neighbor 192.0.2.10 remote-as 64501 ! address-family ipv4 unicast neighbor 10.46.1.100 route-map RPKI-Test in exit-address-family ! route-map RPKI-Test permit 10 match rpki invalid set local-preference 5 |
Теперь настроим маршрутизатор условного злоумышленника, который будет анонсировать сеть сервера DNS Cloudflare (1.1.1.0/24, AS13335) из явно несоответствующей автономной системы 64501:
1 2 3 4 5 6 7 8 |
ip route 1.1.1.0/24 blackhole router bgp 64501 neighbor 192.0.2.1 remote-as 64496 ! address-family ipv4 unicast network 1.1.1.0/24 exit-address-family |
Теперь на первом маршрутизаторе в show ip bgp мы увидим следующую картину:
1 2 |
Network Next Hop Metric LocPrf Weight Path *> 1.1.1.0/24 192.0.2.10 0 5 0 64501 i |
Заключение
Надеюсь, эта статья поможет тебе дожить до массового внедрения RPKI без инцидентов.
Рекомендую почитать документацию FRR по RPKI и RIPE NCC