Ted écrit des trucs

Sur des sujets en lien avec la vie privée, ou parfois pas.

Faire fonctionner un tunnel IP over DNS

— modifié le

J'ai essayé de faire fonctionner une solution IP over DNS sur mon serveur l'autre jour, et après avoir un peu galéré, j'ai fini par y arriver. J'ai été assez désagréablement surpris de la qualité médiocre des tutoriaux disponibles en ligne sur le sujet : la majorité donnent plein de détails inutiles (en expliquant par exemple comment on installe un logiciel) et considèrent qu'une fois qu'on a fait marcher la configuration de base, on a gagné et finissent là le tutoriel (alors qu'en pratique, on a envie de faire autre chose que juste envoyer des pings sur le serveur, et c'est pas évident si on ne sait pas comment faire).

Ce billet est donc une tentative d'expliquer le principe, la configuration et l'utilisation d'un logiciel qui s'appelle iodine et qui fait de l'IP over DNS.

Principe et intérêt

Vous pouvez sauter jusqu'au dernier paragraphe de cette section si vous vous fichez de savoir ce qui se passe à « bas niveau » lors d'un tunnelling IP over DNS et que vous voulez juste savoir comment on arrive à l'étape « se connecter à Internet sans payer depuis un wifi mal protégé ». Ce qui suit est inspiré en grande partie par ce billet publié par Digital Bond sur le sujet.

Quand on utilise Internet, par exemple quand on navigue sur le Web, ce qui se passe « en pratique », c'est que l'ordinateur qu'on utilise envoie des petits paquets IP qui contiennent des informations (par exemple, « je veux récupérer le contenu de cette page », « je veux envoyer tel mail à telle adresse », etc.). Dans ces paquets, le morceau d'information « cette page » ou « telle adresse » est différent de l'information que l'utilisateur a l'impression d'utiliser : il ne s'agit pas d'une adresse web comme https://desfontain.es/blog/, ou d'une jolie adresse mail, mais d'une adresse IP (du genre 5.9.79.154 ou 2a01:4f8:161:9281:4000::beef selon si on utilise IPv4 ou IPv6). Pour transformer la jolie adresse web qu'un humain peut retenir et communiquer facilement en une adresse IP, c'est le protocole DNS qui est utilisé. Donc, quand on tape http://damien.desfontain.es dans un navigateur, le navigateur va commencer par envoyer un paquet disant « je cherche l'adresse IP de damien.desfontain.es » à un serveur DNS local (dont il connaît déjà l'adresse IP), et le serveur DNS en question transmettra cette information jusqu'aux serveurs DNS responsable de la gestion de desfontain.es (dans mon cas, dns102.ovh.net et ns102.ovh.net). Ceux-là connaissent ma zone DNS, et la réponse se trouve dedans.

Lors de cette communication, des « données » sont échangées entre « la machine qui a originellement émis la requête DNS » et les serveurs DNS gérant mon serveur, ici les serveurs d'OVH. Je peux aussi choisir de ne pas déléguer tout ou partie de ma zone DNS, pour que lors d'une requête spéciale, ça ne soit pas les serveurs d'OVH qui soient interrogés mais le mien. Dans l'exemple qui suit, je modifie ma zone DNS pour dire « toutes les requêtes concernant des adresses finissant par .dns.desfontain.es sont gérées par mon serveur »… Et c'est ça qui permet de détourner l'utilité première du DNS pour faire des choses intéressantes.

En effet, supposons maintenant que la connexion qu'on utilise laisse passer les requêtes DNS mais bloque le reste. Rien ne m'empêche de coder des choses dans les données qui sont échangées par ce biais entre mon ordinateur local et mon serveur… Par exemple, des paquets IP « normaux ». C'est le principe de l'IP over DNS : on va envoyer depuis le client tout un tas de requêtes DNS de la forme « envoie-moi l'adresse associée à nudhrpbhpbh.dns.desfontain.es », où le serveur va répondre « c'est [une certaine adresse], et par ailleurs voilà un peu de données pour que le paquet fasse la bonne taille : lbhwhfgybfggurtnzr », et ce charabia correspond en fait à un échange contenant des données « intéressantes » encodées de cette façon.

Ça paraît tordu, mais en fait c'est utile : il est très fréquent (dans les gares, hôtels, aéroports, et même chez les particuliers munis de certaines box) de rencontrer des réseaux Wifi qui sont non protégés, mais qui redirigent toutes les requêtes HTTP vers une page (« portail captif ») qui demande des identifiants, ou propose de payer pour accéder à Internet par ce réseau. Ces réseaux bloquent toutes les requêtes, à part les requêtes DNS, qui sont nécessaires pour permettre la redirection des pages Web normales vers le portail captif, et la résolution des adresses « autorisées » (par exemple, la page d'authentification). Je crois. En fait, je ne suis pas certain de la raison profonde pour laquelle il n'existe pas de système de portail captif qui bloque les requêtes DNS sans embêter les utilisateurs normaux, mais vu que ça n'existe pas, je suppose qu'il doit y avoir une raison profonde. Si quelqu'un la connaît, ça m'intéresse.

Donc, faire passer tous les paquets IP par des requêtes DNS, ça sert à se connecter à Internet depuis un gros paquet d'endroits sans payer ni avoir les identifiants de tous les grands fournisseurs d'accès à Internet qui mettent des réseaux publics dans la box de leurs abonnés. Ça a l'air super cool comme ça donc avant qu'un lecteur enthousiaste ne passe une demi-heure à configurer ça et soit déçu à la fin, je préviens maintenant : même quand ça marche, c'est vraiment très, très lent, et la connexion est très loin d'être stable. C'est plus une solution d'appoint, et une démonstration de faisabilité intéressante.

Prérequis

Il faut pour utiliser cet outil, disposer d'un serveur sur lequel on peut installer iodine, et posséder un nom de domaine sur lequel on peut manipuler les entrées DNS. Je vais dans toute la suite supposer que les deux machines sont sous Linux.

On va dire que le serveur dont vous disposez a comme adresse coucou.com et que son IP est 42.17.42.17.

Configuration

Installation

Installer iodine sur le client et le serveur. Sous Debian et dérivés, on tape dans une console root :

aptitude install iodine

et pour les autres systèmes, allez voir le site officiel pour avoir les instructions d'installation.

Modification de la zone DNS

Ajouter les entrées suivantes à la zone DNS du serveur :

dns     IN  A   42.17.42.17
t       IN  NS  dns.coucou.com

et attendre que ça se propage (sinon, on fait des tests et on comprend pas pourquoi ça marche pas), quelques heures devraient être plus que largement suffisantes. C'est utile pour dire plus tard à iodine quel genre de requête écouter : en l'occurence, les requêtes sur t.coucou.com et non pas juste sur coucou.com comme en temps normal.

On peut remplacer "dns" par n'importe quoi, et "t" par n'importe quoi d'autre - par contre, le fait que "t" ne fasse qu'un seul caractère est relativement important, ça laisse plus de place pour les données et donc ça accélère la vitesse de connexion du tunnel DNS.

Lancement du démon sur le serveur

Lancer en root sur le serveur :

iodined 10.0.0.1 t.coucou.com

ce qui équivaut à lui dire « lance le démon d'iodine, donne l'adresse IP 10.0.0.1 au serveur, et fais-le écouter sur t.coucou.com ». Le programme demande un mot de passe, qui peut être n'importe quoi jusqu'à 32 caractères. Après cette commande, on peut vérifier notamment par :

ifconfig

(en root, toujours) qu'une nouvelle interface est apparue dans la liste (elle s'appelle en général dns0), c'est à travers elle que va passer tout le trafic géré par iodine.

Lancement du tunnel sur le client

Lancer en root sur le client :

iodine -f 42.17.42.17 t.coucou.com

l'option -f permet de lancer ça au premier plan et donc, de savoir si jamais ça plante. Évidemment, du coup, si on ferme le terminal dans lequel on a lancé la commande, ça marche plus. Dans la liste des lignes qui s'affichent alors, il devrait y avoir :

Setting IP of dns0 to 10.0.0.2
Server tunnel IP is 10.0.0.1

(avec potentiellement 10.0.0.autrechoseque2). Et comme pour le serveur, une catégorie de plus (aussi appelée dns0) devrait être apparue dans ce que renvoie (en root) :

ifconfig

Pour vérifier que tout va bien, on peut essayer de lancer :

ping 10.0.0.1

depuis le client, normalement ça devrait retourner quelque chose. De même, faire un ping sur 10.0.0.2 depuis le serveur devrait aussi retourner quelque chose. Sinon, hmmm, il y a un problème, et il faut vraisemblablement essayer de fouiller dans le manuel de iodine et de jouer avec les options.

Faire passer les paquets d'une interface à l'autre

Si on a réussi à pinger 10.0.0.1 depuis le client, ça veut dire qu'on arrive à lui dire bonjour en passant par DNS : c'est bien ! Maintenant, on aimerait faire un truc encore plus cool et accéder à l'extérieur. Par défaut, bien sûr, ça n'est pas possible : du point de vue du serveur, tout ce qui passe par l'interface dns0 n'a aucune raison d'être renvoyé sur autre chose. Il faut donc activer (en root) le forwarding d'une interface à l'autre :

sysctl -w net.ipv4.ip_forward=1

et lui dire plus précisément de renvoyer tout ce qui vient de 10.0.0.* (la plage d'adresses IP associées à iodine) vers eth0, et pareil dans l'autre sens (toujours en root) :

iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -o eth0 -j MASQUERADE

Si l'interface « normale » du serveur n'est pas eth0 (il faut aller voir dans ce que renvoie ifconfig pour le savoir), il faut bien sûr remplacer eth0 par l'interface pertinente par laquelle on se connecte à Internet en temps normal.

Tester si on arrive à dire bonjour à Internet

Normalement, à ce stade, on devrait atteindre l'extérieur en passant par notre jolie configuration. Vérifier que tout marche toujours bien et relancer les commandes iodined/iodine si besoin (la connexion peut mourir s'il ne passe rien dessus pendant trop longtemps), et essayer d'envoyer un message vers l'extérieur en passant par notre interface depuis le client :

ping -I dns0 google.com

si ça marche, joie, bonheur et allégresse.

Faire passer du SSH à travers notre tunnel

Bon, c'est bien gentil, mais on a envie de faire passer autre chose que ping par notre système. Par exemple, on voudrait bien faire passer du SSH. Mais contrairement à ping, il est impossible de dire à ssh « passe par telle interface », ça se décide au niveau du noyau. Il faut donc bidouiller le routage IP pour dire au noyau « envoie tout ce qui va vers telle machine par notre serveur ». Ça se fait de la façon suivante, en admettant que l'adresse IP du serveur SSH auquel vous vouliez vous connecter est 71.24.71.24 :

ip route add 71.24.71.24 via 10.0.0.1

(bien entendu, il faut faire ça en root, un utilisateur normal n'a pas le droit de décider de ce genre de truc). Maintenant, si on essaie :

ssh user@71.24.71.24

(ou bien l'URL du serveur et non pas son IP), ça va automatiquement passer par le tunnel. Si ça marche, joie, bonheur, et désillusion lorsqu'on ce rend compte à quel point ça lagge. C'est le prix de passer par des paquets DNS ! C'est pas vraiment prévu pour ça. Si ça marche pas, il y a quelque chose qui ne va pas.

Faire passer une connexion au Web à travers notre tunnel

Pour aller plus loin que « se connecter à une machine en SSH », on peut ajouter plus de choses dans la table de routage. Au lieu de router une machine en particulier, on peut router une plage d'adresses IP. Pour lui dire « ne prends en compte que les n premiers bits », on utilise la notation /n. Par exemple, la commande :

ip route add 71.24.0.0/16 via 10.0.0.1

signifie « fais passer tout ce qui vient d'une adresse en 71.24.*.* via 10.0.0.1 ». Ainsi, si un navigateur essaie d'accéder à un site appartenant à cette adresse IP, la requête va passer par le système d'IP over DNS. Le problème c'est qu'on ne veut pas que les requêtes DNS elles-mêmes passent par là (le DNS over DNS, c'est relativement dénué d'intérêt). La solution la plus simple est vraisemblablement de disposer d'un serveur SSH, et de passer par un tunnel SSH. Pour ça, on fait comme au point 7) en faisant passer les requêtes envoyées à l'adresse du serveur par 10.0.0.1, puis on lance :

ssh -ND 3128 user@serveur.com

sur le client. Cette commande ne renvoie rien et c'est normal, on laisse la fenêtre ouverte, on va régler les paramètres de proxy de son navigateur pour lui dire d'utiliser le proxy localhost, sur le port 3128, en précisant que c'est un proxy Socks version 5, et hop. Tout ce qui passe par le navigateur passera par le tunnel SSH, qui lui-même passe par le pont formé par iodine.

Magie ! On est essentiellement en train de faire de l'HTTP over SSH over DNS, ce qui est quand même rigolo.

Conclusion

Bon, revenons sur Terre, tout ça rame quand même affreusement. Si vous êtes arrivés jusqu'à ce stade, vous avez probablement eu l'occasion de réaliser que c'était quand même très, très moyennement utilisable. La « bonne » façon d'utiliser un tunnel DNS en pratique, c'est probablement de ne faire que du SSH, et d'aller chercher les informations qu'on veut récupérer en utilisant l'excellent Weboob ou bien un navigateur en mode texte. Ça permet que ça soit le serveur qui fasse les « vraies » requêtes qui prennent plein de bande passante, et que la seule chose qui passe du serveur au client, ce soit les requêtes et le texte des pages transformées. Pour avoir testé ça dans diverses situations, c'est généralement lent mais utilisable.

Merci à a3nm et louis pour leur relecture et corrections.

Tout ce que je raconte ici sont mes opinions personnelles, pas celles de mon employeur.
Je suis toujours ravi de recevoir des réactions, critiques, questions, ou autres commentaires sur ce que j'écris. Envoyez-moi ce qui vous chante à se.niatnofsed@neimad.