WireGuard DNS DNAT: сохранить рабочим обратный путь
DNAT переписывает назначение, а не источник. Решите, как вышестоящий resolver сможет ответить, прежде чем добавлять правило, и держите каждое совпадение узким.
Направление DNS-запроса клиента WireGuard на другой resolver выглядит как одно правило, а ведёт себя как три решения. Назначение нужно переписать, пакет нужно передать, а ответ должен найти путь обратно через состояние трансляции, созданное шлюзом. Пропустите третье — и запрос чисто уйдёт, пока клиент ждёт вечно.
Возьмём пример: авторизованные клиенты в 10.77.0.0/24 приходят через wg0, запрашивают объявленный адрес 10.77.0.1 и нуждаются в resolver по адресу 203.0.113.53. Вышестоящий адрес — это заглушка для документации, а не работающий сервис.
Перенаправление запроса меняет только его назначение
Правило NAT назначения в PREROUTING переписывает адрес до принятия решения о маршрутизации, поэтому пакет затем маршрутизируется к новому назначению вместо старого. Адрес источника клиента этой операцией не затрагивается. Поэтому правило, которое выглядит полным, всё ещё может давать тишину: запрос теперь идёт к resolver, который никогда не слышал о частном диапазоне клиента. Шлюз перевёл одно поле и унаследовал вторую проблему.
Оба транспорта DNS нуждаются в согласованных правилах, потому что клиент возвращается к TCP, когда ответ усекается.
Правила ниже выбирают один входящий интерфейс, один диапазон клиентов и один исходный адрес назначения. Больше ничего на шлюзе не затрагивается, а запрос самого шлюза вообще не совпадает.
sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A PREROUTING -i wg0 -s 10.77.0.0/24 -d 10.77.0.1 -p udp --dport 53 -j DNAT --to-destination 203.0.113.53:53
iptables -t nat -A PREROUTING -i wg0 -s 10.77.0.0/24 -d 10.77.0.1 -p tcp --dport 53 -j DNAT --to-destination 203.0.113.53:53
Обратный путь — отдельное решение
Две схемы позволяют ответу вернуться, и ранний выбор одной экономит сеанс отладки. Либо у вышестоящего resolver есть маршрут обратно к 10.77.0.0/24 через этот шлюз, либо шлюз транслирует источник на выходе, чтобы ответ пришёл на адрес, который вышестоящая сторона уже знает. Работают оба; только один соответствует уровню контроля, который у вас есть над вышестоящей стороной.
Маршрутизация заслуживает первой попытки, потому что сохраняет адрес клиента видимым для resolver и избегает второго слоя трансляции. Когда вышестоящая сторона вне вашего контроля, трансляция источника на конкретном выходном интерфейсе — практичный выбор.
Ограничьте эту трансляцию так же узко, как перенаправление, иначе правило станет NAT общего назначения для всего, что случайно под него подходит:
iptables -t nat -A POSTROUTING -s 10.77.0.0/24 -d 203.0.113.53 -o eth0 -p udp --dport 53 -j MASQUERADE
iptables -t nat -A POSTROUTING -s 10.77.0.0/24 -d 203.0.113.53 -o eth0 -p tcp --dport 53 -j MASQUERADE
Отслеживание соединений держит отображение в паре, поэтому ответ переписывается обратно в исходный tuple клиента на обратном пути. Без выходного правила resolver отправляет ответ на частный адрес, до которого не может добраться, и клиент видит тайм-аут вместо ошибки.
Пишите правила уже, чем кажется задача
Разрешение на пересылку относится к той же области. Разрешите поток от клиента к resolver и обратный трафик, который conntrack уже распознаёт, вместо того чтобы принимать каждое соединение DNAT на машине. Сплошное разрешение также санкционирует перенаправление портов, не имеющее отношения к DNS.
Собственный resolver шлюза — это другой путь кода. Запрос, выданный локально, проходит через OUTPUT, поэтому проверка из оболочки шлюза не упражняет ни правило PREROUTING, ни политику пересылки. Вот почему локальный тест может пройти, пока каждый клиент терпит неудачу, и обратное столь же обычно.
Здесь ничего не охватывает DNS поверх HTTPS на порту 443 или DNS поверх TLS на порту 853, и ничего не охватывает IPv6. Клиент с настроенным resolver IPv6 с удовольствием проигнорирует всю схему.
Что доказывает свежий запрос
Отслеживание соединений помнит прежние отображения, поэтому запрос, отправленный до изменения правила, может быть обслужен из старого состояния и заставить новое правило выглядеть сломанным. Начните с нового порта источника и, там где клиент кэширует ответы, с нового имени.
dig @10.77.0.1 example.com A +short
conntrack -L -p udp --dport 53 | head
Первая команда показывает, разрешается ли путь клиента вообще, а вторая — трансляцию, которую шлюз действительно установил, включая оба направления tuple. Отображение, указывающее на правильный resolver, но с застывшим на нуле счётчиком ответов, — это проблема обратного пути, а не фильтрации.
Прежде чем что-либо перенаправлять, убедитесь, что resolver знает ожидаемые имена. Замена, которая отвечает на публичные имена, но не на вашу частную зону превращает работающий поиск в NXDOMAIN, который выглядит как сломанный туннель. Проверьте resolver напрямую, со шлюза, прежде чем будет задействован путь клиента. А справочник по расширениям iptables описывает семантику DNAT и masquerade, на которую опираются эти правила.