Faire fonctionner un tunnel IP over DNS
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.