Два инженера с одним и тем же шлюзом OpenBSD часто дадут вам два разных набора правил для доступа к удалённой сети, и оба можно защитить. Выбор между трансляцией источника и обратным маршрутом — вовсе не вопрос синтаксиса. Всё сводится к тому, что удалённая сторона уже знает о 192.168.88.0/24 и что вам нужно сохранить в журналах. Ошибётесь здесь, и строка nat-to будет выглядеть совершенно правильно, пока ответы исчезают в удалённом шлюзе по умолчанию.

Маршрутизация и перезапись отвечают на разные вопросы

Таблица маршрутизации решает, что 172.20.0.0/16 достижим через tun0, и это решение принимается до того, как будет запрошена любая трансляция pf. Затем трансляция источника меняет заголовок пакета, чтобы удалённая сторона видела адрес туннеля шлюза, а не хост LAN. Ни один механизм не заменяет другой: правило nat не может создать маршрут, а маршрут не может убедить удалённый маршрутизатор, который никогда не слышал о вашей частной подсети. Когда у удалённой стороны нет обратного пути, ответы отбрасываются или пересылаются на её шлюз по умолчанию, и никакая настройка правил это не исправит.

Проще говоря, пересылка отвечает на вопрос, куда идёт трафик, а трансляция — откуда он выглядит приходящим. Держите их в отдельных мысленных ящиках, пока проектируете набор правил.

Три условия, которые всё решают

Задайте три вопроса по порядку, и проект обычно выбирается сам. Есть ли у удалённой сети статический маршрут на адрес туннеля для 192.168.88.0/24, или её администратор управляет маршрутизаторами на этом пути? Если да, пропустите LAN-адреса без изменений и полностью откажитесь от трансляции. Нужна ли вам видимость по каждому клиенту на удалённой стороне — для политики доступа или для журнала аудита? Если да, сохранение источника стоит трудов по маршрутизации. Достаточно ли стабилен адрес туннеля, чтобы прописать его в удалённый маршрут, или он меняется между переподключениями?

Если любой ответ «нет» или неизвестен, nat-to — прагматичный вариант, и он же обратимый. Правило трансляции убирается одной строкой, как только удалённая сторона добавит нормальный маршрут. Обратный маршрут, который так и не был установлен, стоит вам одной ветки поддержки каждый раз, когда кто-то спрашивает, почему туннель работает только в одну сторону.

Разместите фрагмент так, чтобы pf действительно его оценивал

Приведённый ниже фрагмент предполагает, что LAN — это 192.168.88.0/24, удалённая сеть — 172.20.0.0/16, а интерфейс туннеля — tun0. Ничто в нём не выбирает следующий хоп. Он лишь перезаписывает источник у пакетов, которые уже уходят через туннель.

match out on tun0 inet from 192.168.88.0/24 to 172.20.0.0/16 nat-to (tun0)
pass in on em0 inet from 192.168.88.0/24 to any keep state

Две детали в этой первой строке несут основную нагрузку. Скобки вокруг tun0 говорят pf читать адрес интерфейса каждый раз при построении записи состояния, что важно, когда после переподключения туннелю присваивается новый адрес. Слово match означает, что правило перезаписывает и затем проваливается дальше, поэтому отдельному pass-правилу всё равно нужно пропустить поток. Интерфейс, указанный в строке match, также должен быть тем, который выбирает таблица маршрутизации, иначе pf оценит правило на другом исходящем пути, чем тот, по которому пакет реально идёт.

Не забудьте про сторону ядра. Пересылка IPv4 должна быть включена, а stateful pass-правила для туннеля и LAN должны существовать до того, как всё это обретёт смысл. Раздел PF NAT в FAQ OpenBSD — правильная ссылка на порядок ключевых слов.

Проверьте набор правил, прежде чем доверять трафику

Сначала проверьте синтаксис командой pfctl -nf /etc/pf.conf, которая разбирает файл, не загружая его. Затем загрузите политику и перечитайте, что ядро действительно держит, потому что чисто разобранное правило всё равно может быть затенено или привязано к неверному интерфейсу. pfctl -sn печатает правила трансляции в загруженном виде, а pfctl -sr печатает правила фильтрации в порядке вычисления.

Затем наблюдайте за проводом. На шлюзе tcpdump -ni tun0 'host 172.20.0.10' показывает, является ли адрес источника, выходящего из туннеля, адресом туннеля или хостом LAN. Быстрый netstat -rn -f inet подтверждает, на какой интерфейс разрешается назначение. Как только трафик пошёл, pfctl -ss перечисляет записи состояния; если поток не создал состояние, пакет ни разу не совпал с правилом, которое его создаёт.

Если сторона туннеля выглядит правильно, а ответы всё не приходят, прекратите редактировать pf. Оставшаяся неисправность на удалённом маршрутизаторе, где ответ отправляется на частный адрес, недостижимый с той стороны.

Когда трансляция стоит дороже, чем экономит

Трансляция скрывает клиентов, и иногда это как раз неудачный обмен. Перекрывающиеся подсети — один случай: если удалённая сторона тоже использует 192.168.88.0/24 внутри, представление LAN-хостов как шлюза делает коллизию хуже, а не лучше. Политики IPsec, выбирающие трафик по паре адресов, — другой случай, потому что база политик безопасности сопоставляется до трансляции и никогда не видит перезаписанный пакет. Любое развёртывание, которое тарифицирует, ограничивает или аудирует по пользователю, тоже теряет идентичность, которая нужна ему в тот момент, когда источник сжимается до одного адреса.

Переносимые производные pf — отдельная ловушка. Продукты, генерирующие собственные файлы правил, ожидают, что NAT будет настроен через их интерфейс, а отредактированные вручную фрагменты перезаписываются при следующей перезагрузке правил. Наборы правил, скопированные из руководств, написанных до появления ключевого слова match, не загрузятся на текущем ядре, поэтому перенесите синтаксис и прочитайте ошибку вместо того, чтобы угадывать.

Правило принятия решений, которое выживает при контакте с реальными сетями, коротко: установите обратный маршрут, когда можете, транслируйте, когда не можете, и запишите, какое из трёх условий заставило вас выбрать именно это. Эта заметка экономит следующему человеку полдня.

Похожие статьи