Баг в файрволе pf

8 июля 2015, 14:25

Предисловие

В конторе мы вынужденно используем FreeBSD 9.3 на BRAS'ах. Вызвано это тем, что на 10-ой фре есть какой-то косяк с mpd (у нас с его помощью PPPoE-соединения от клиентов обслуживаются). И mpd периодически виснет при значительном изменении числа подключений. Т. е. когда много клиентов одновременно подключается или отключается, mpd зависает в состоянии umtxn. Я писал по этому поводу в уже открытый PR, но движухи по нему нет.

О проблеме

Но речь не об этом. В общем, на фре 9.3 в файрволе pf обнаружился занятный баг. Баг относится к работе NAT на pf. Проявляется так:

  1. Клиент шлёт через NAT запрос на какой-то сервер в интернете;
  2. Сервер ему отвечает;
  3. Но за время ответа у клиента этот порт, с которого он отправлял свой запрос, почему-то закрылся;
  4. Клиент примет пакет от сервера и ответит серверу ICMP пакетом с типом port unreachable

Тут-то и проявляется баг — pf'овский NAT некорректно натит этот ответный ICMP-пакет, и в итоге с внешнего интерфейса улетает ICMP-пакет с серым SRC IP клиента и белым DST IP того сервера, куда этот пакет должен быть доставлен.

Т. е. допустим, у вас адрес NAT-интерфейса — 1.1.1.1, адрес сервера в интернете 2.2.2.2. И адрес клиента 192.168.0.1. В итоге с ВНЕШНЕГО интерфейса NAT'a улетит пакет с адреса 192.168.0.1 на адрес 2.2.2.2. Т. е. трансляции адреса клиента во внешний адрес NAT (1.1.1.1) не произойдёт.

Кровавые подробности

Если быть совсем точным, то трансляция ICMP-пакета таки происходит. Но не везде и не до конца :-)

Немного теории

ICMP-пакеты подразделяются на несколько типов. Причём в каждом типе есть ещё несколько подтипов. В данном случае клиент генерирует пакет типа 3 «destination unreachable». И подтипа тоже 3 — «port unreacable». Подробности можно почитать в man icmp, там есть табличка с типами и подтипами (подтипы называются codes, коды). В пакет типа port unreacahble клиент вкладывает заголовок того пакета, который вызвал генерацию сообщения ICMP «port unrecahable» (назовём для краткости такой пакет «пакетом-инициатором»). Ну, чтобы сервер, получивший от клиента ICMP destination unreachable понимал, на какой конкретно его пакет клиент ответил, что порт недоступен. И вот расследование показало, что как раз этот заголовок, пакета-инициатора транслируется файрволом нормально. Но сам ICMP-пакет — нет.

Суровая практическая реальность

Итак, мы приняли, что локальный IP клиента у нас 192.168.0.1, а внешний IP, в который NAT'ится клиентский трафик — 1.1.1.1. Таким образом, получается примерно такой формат ICMP-пакета от клиента обратно на сервер 2.2.2.2 (до трансляции):

Заголовок ICMP SRC IP: 192.168.0.1 DST IP: 2.2.2.2
Заголовок пакета-инициатора SRC IP: 2.2.2.2 port 80 DST IP: 192.168.0.1 port 12345

То есть, клиент со своего адреса 192.168.0.1 оповещает сервер 2.2.2.2, что типа, «бро, ты мне отправил пакет со своего IP 2.2.2.2 и порта 80 на мой адрес 192.168.0.1 и порт 12345. Ну так вот, довожу до твоего сведения, что этот порт у меня закрыт».

После трансляции через NAT пакет должен модифицироваться следующим образом:

Заголовок ICMP SRC IP: 1.1.1.1 DST IP: 2.2.2.2
Заголовок пакета-инициатора SRC IP: 2.2.2.2 port 80 DST IP: 1.1.1.1 port 54321

То есть, NAT уже от своего имени проговаривает серверу 2.2.2.2: «бро, ты мне отправил пакет со своего IP 2.2.2.2 и порта 80 на мой адрес 1.1.1.1 и порт 54321. Ну так вот, довожу до твоего сведения, что этот порт у меня закрыт».

Обращаю внимание, что порт на клиентской стороне ДО и ПОСЛЕ трансляции могут отличаться. Т. е. то, что у клиента слушает на порту 12345, на NAT'е будет на порту 54321 (ну, или на любом другой, который будет свободен на момент установки соединения клиентом, и который NAT выберет для трансляции). Ну это так, для сведения, сейчас это роли не играет.

Так вот, из-за бага в pf пакет транслируется следующим образом:

Заголовок ICMP SRC IP: 192.168.0.1 DST IP: 2.2.2.2
Заголовок пакета-инициатора SRC IP: 2.2.2.2 port 80 DST IP: 1.1.1.1 port 54321

Т. е. NAT в pf транслирует только заголовок пакета-инициатора, идущий нагрузкой в ICMP-пакете типа «destination unreachable», но не транслирует заголовок самого ICMP-пакета.

Заключение

Написал сообщение в рассылку freebsd-net. Там товарищ Kristof Provost попросил проверить, есть ли такое же поведение в -CURRENT, и если да, то он посмотрит, в чём там проблема. Ну, соберу -CURRENT на досуге, попробую.

А, кстати, ещё один момент. pf не только некорректно натит такие пакеты, но и также не может блокировать этот трафик. Т. е. правило вида

block out quick inet proto icmp from 192.168.0.0/24 to any

тоже не работает для таких пакетов. Похоже, pf анализирует только содержащийся в нагрузке заголовок пакета-инициатора, а не самого ICMP-пакета.

UPDATE

Собрал -CURRENT, проверил. Это поведение полностью повторяется и в -CURRENT. Нарисовал багрепорт по этому поводу, авось когда-нибудь починят. На текущий момент подобный пытающийся улететь наружу трафик блокируется с помощью IPFW.

Популярное