Diriger la requête DNS d’un client WireGuard vers un autre résolveur ressemble à une règle et se comporte comme trois décisions. La destination doit être réécrite, le paquet doit être transmis, et la réponse doit retrouver son chemin à travers l’état de traduction créé par la passerelle. Sautez la troisième et la requête part proprement tandis que le client attend indéfiniment.

Prenons l’exemple de clients autorisés dans 10.77.0.0/24 qui arrivent par wg0, interrogent l’adresse annoncée 10.77.0.1 et ont besoin d’un résolveur à 203.0.113.53. L’adresse amont est un espace réservé de documentation, pas un service qui fonctionne.

Rediriger une requête change seulement sa destination

Une règle de NAT de destination dans PREROUTING réécrit l’adresse avant que la décision de routage ne soit prise, si bien que le paquet est ensuite routé vers la nouvelle destination plutôt que l’ancienne. L’adresse source du client n’est pas touchée par cette opération. C’est pourquoi une règle qui paraît complète peut encore produire du silence : la demande voyage désormais vers un résolveur qui n’a jamais entendu parler de la plage privée du client. La passerelle a traduit un champ et hérité d’un second problème.

Les deux transports DNS ont besoin de règles correspondantes, car un client revient à TCP lorsqu’une réponse est tronquée.

Les règles ci-dessous sélectionnent une interface d’entrée, une plage de clients et une adresse de destination d’origine. Rien d’autre sur la passerelle n’est affecté, et une requête de la passerelle elle-même ne correspond pas du tout.

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

Le chemin de réponse est une décision distincte

Deux dispositions permettent à la réponse de revenir, et en choisir une tôt épargne une session de débogage. Soit le résolveur amont dispose d’une route de retour vers 10.77.0.0/24 via cette passerelle, soit la passerelle traduit la source en sortie pour que la réponse arrive à une adresse que l’amont connaît déjà. Les deux fonctionnent ; une seule correspond au contrôle que vous avez sur l’amont.

Le routage mérite le premier essai, car il garde l’adresse du client visible pour le résolveur et évite une seconde couche de traduction. Quand l’amont échappe à votre contrôle, la traduction de source sur l’interface de sortie précise est le choix pratique.

Délimitez cette traduction aussi étroitement que la redirection, sinon la règle devient un NAT à usage général pour tout ce qui vient à correspondre :

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

Le suivi des connexions garde la correspondance appariée, si bien que la réponse est réécrite vers le tuple d’origine du client au retour. Sans la règle de sortie, le résolveur envoie sa réponse vers une adresse privée qu’il ne peut pas atteindre, et le client voit un délai d’attente plutôt qu’une erreur.

Écrivez les règles plus étroites que la tâche n’en a l’air

L’autorisation de transfert relève du même périmètre. Autorisez le flux du client vers le résolveur et le trafic de retour que conntrack reconnaît déjà, plutôt que d’accepter chaque connexion DNAT sur la machine. Une acceptation globale autorise aussi des redirections de ports qui n’ont rien à voir avec DNS.

Le résolveur propre à la passerelle est un autre chemin de code. Une requête émise localement passe par OUTPUT, donc tester depuis le shell de la passerelle n’exerce ni la règle PREROUTING ni la politique de transfert. Voilà pourquoi un test local peut réussir alors que chaque client échoue, et l’inverse est tout aussi fréquent.

Rien ici ne couvre DNS sur HTTPS au port 443 ni DNS sur TLS au port 853, et rien ne couvre IPv6. Un client doté d’un résolveur IPv6 configuré ignorera allègrement toute la disposition.

Ce que prouve une requête fraîche

Le suivi des connexions se souvient des correspondances antérieures, donc une requête envoyée avant un changement de règle peut être répondue depuis l’ancien état et faire paraître la nouvelle règle cassée. Commencez par un nouveau port source et, là où le client met les réponses en cache, par un nouveau nom.

dig @10.77.0.1 example.com A +short
conntrack -L -p udp --dport 53 | head

La première commande montre si le chemin client résout du tout, et la seconde montre la traduction que la passerelle a réellement installée, y compris les deux sens du tuple. Une correspondance qui pointe vers le bon résolveur avec un compteur de réponses bloqué à zéro est un problème de chemin de retour, pas de filtrage.

Confirmez que le résolveur connaît les noms que vous attendez avant de rediriger quoi que ce soit. Un remplacement qui répond aux noms publics mais pas à votre zone privée transforme une recherche fonctionnelle en NXDOMAIN qui ressemble à un tunnel cassé. Testez le résolveur directement, depuis la passerelle, avant que le chemin client n’entre en jeu. Et la référence des extensions iptables documente la sémantique DNAT et masquerade dont ces règles dépendent.

Articles similaires