Un point sur: NAT et Hole punching

Portrait de fira

Ces derniers jours, en temps qu'étudiant administrateur réseau, j'ai pu observer le gouffre de compréhension réseau qu'on peut avoir avec des gens plus orientés développement...
J'ai écrit quelques notes de sujets à aborder a ce propos sur le tableau du club, et la même question est revenue très souvent: qu'est ce que / comment fonctionne le hole punching NAT ?
Voilà quelques éléments de réponse.

Un petit rappel sur TCP, IP et UDP

IP est un protocole permettant à des machines de communiquer sur Internet ou des réseaux locaux.
On leur assigne une adresse IP qu'elles utilisent pour s'envoyer des messages.
TCP et UDP permettent de transporter des données dans ces messages.
Le point important pour cet article, c'est de savoir que ces deux protocoles disposent de champs ports source et destination:
on envoie donc un message ou fait une connection depuis un port, vers un autre --
ce mécanisme étant utilisé pour identifier les connections et les multiplexer (exemple, port 80 pour le web, 25 pour smtp, etc etc)

Qu'est ce qu'un firewall NAT ?

Le NAT -- Network Address Translation -- est un mécanisme inventé pour pallier au manque d’adresses IPv4 sur Internet.
Il y a plusieurs implémentation du NAT sur Internet, mais l'une d'entre elles est très largement déployée...

Elle est très facile à comprendre avec une analogie:
Imaginez être dans un monde ou il n'y a de la place sur les enveloppes que pour noter les adresses des bâtiments.
Le jour ou les immeubles sont inventés, un problème se pose: à quel locataire sont destinées les lettres ?
La réponse du NAT, c'est de placer un concierge. Lorsque vous envoyez une lettre depuis l'appartement 512 vers le 3 Rue A, le concierge remplace l'appartement par l'adresse de l'immeuble, mettons 8 rue B.
Quand plus tard une réponse arrive (donc du 3 Rue A vers le 8 rue B), le concierge se souvient que vous discutiez avec cette adresse, et remplace le destinataire par le numéro d'appartement. Voilà!

Sur Internet c'est pareil: chez certaines entreprises, écoles, et surtout avec votre box, on retrouve du NAT pour combler le manque d'adresses.
Votre ordinateur possède donc une adresse "privée", qui n'a pas de sens sur Internet (192.168.1.25 ou appartement 512),
et votre box/routeur se charge de faire la transition avec son adresse publique (147.215.81.100 ou 3 Rue A, 66477 VillePerdue).
Cette transition se base généralement sur un combo des IP et Ports applicatifs: si je contacte A du port N vers M, la réponse de B du port M vers N sera pour moi.

Ce système pose deux gros problèmes:
- Premièrement, celà néccessite une inspection des paquets par le routeur, ce qui prends du temps et de la mémoire. Ce n'est plus trop un problème aujourd'hui.
- Le concierge/box enregistre les connections sortantes. Mais que se passe t'il si quelqu'un initie la connexion en premier ? Impossible de savoir à qui elle est destinée.
Ce deuxième effet est malheureusement utilisé sur beaucoup de systèmes comme "sécurité" pour remplacer un firewall alors que ce n'est qu'un effet secondaire, mais c'est un autre problème...

Un petit mot sur l'uPNP et le port forwarding

Pour corriger ce genre de problèmes on dispose de deux méthodes:
- Le "Port Forwarding", qui associe statiquement certains ports à une personne -- elle sera donc garantie de recevoir ces connections entrantes,
- L'uPNP, très utilisé dans les box et applis multimédia, qui permet de détecter automatiquement qu'une telle connection va se faire et configurer l'association
La première méthode n'est pas très pratique car requiert une configuration manuelle.
La deuxième est automatique, mais ne fonctionne que pour certains protocoles, des flux non-cryptés, et est un énorme trou de sécurité (je vous laisse chercher pourquoi/comment, de nombreuses explications sont disponibles sur internet)

Le "Hole Punching"

Ce système est particulièrement gênant dans un contexte d'applications peer-to-peer.
Si vous êtes habitués de ce genre d'applis, ou par exemple de DCC sur IRC, vous saurez qu'on sépare alors les gens en deux catégories: actif et passif.
Les clients actifs peuvent recevoir des connections, tandis que ceux passifs ne peuvent qu'envoyer des demandes de connexion, par la présence d'un firewall NAT pas ou mal configuré.
On peut alors avoir des connections entre deux clients actifs, ou d'un client passif vers actif, mais pas entre deux clients passifs !
C'est un très gros problème pour des applications type Skype: il faut qu'au moins un des deux utilisateurs configure sa box. Pas top pour une appli grand public.
Le Hole Punching permet de contourner ce problème en manipulant les mécanismes du NAT pour permettre une connexion malgré tout.

Le point de rencontre

Il faut savoir que pour que les deux clients puissent se coordonner pour cette manœuvre, il y ont besoin d'un point de rencontre.
C'est typiquement un serveur public et accessible sur Internet, qui va permettre aux clients de communiquer indirectement (on retrouve de nombreux tels schémas en Peer2Peer)

Une question qui reviens souvent est celle de BitTorrent et des liens Magnet: le lien magnet ne donnant pas de serveur, comment ca fonctionne ?
Là encore, ce n'est pas magique. Le lien magnet désigne une 'ressource' commune, en l’occurrence le fichier à télécharger.
Cependant pour pouvoir contacter d'autres utilisateurs on a toujours besoin d'un point de rencontre: l'application client torrent utilisée se connecte à des serveurs, codés en dur, qui vont mettre en relation avec d'autres clients.

La méthode la plus simple: "Simultaneously Open"

Si vous connaissez un peu TCP, vous pouvez savoir que la connexion s'effectue via un "Three-way handshake" : demande de synchro (SYN), réponse de synchro (SYN-ACK), confirmation (ACK).
Il faut qu'une application soit en train d'écouter le port pour accepter cette connection.

Un mode dont on parle beaucoup moins souvent est le "Simultaneously Open": si deux hôtes tentent de se connecter à l'autre en même temps (SYN des deux côtés), il peuvent établir une connexion malgré tout !
Et même dans le cas ou un seul des deux arriverait a destination, les NAT ont enregistré la connexion -- donc une connexion traditionnelle peut ensuite d'établir.
Pour les curieux, on peut combiner les deux en un "Split-Handshake" -- qui pas beaucoup d'intêret, mais l'idée est intéressante (des infos sur l'implication d'une telle connexion sont dispo sur internet).

Une petite démo de ce genre de connexion. Considérez deux clients, chacun derrière un NAT:
- hihix, 10.81.3.2, derrière le NAT du Club*Nix (147.215.81.100)
- angel, 10.241.0.200, derrière un NAT perso (62.210.180.233)

Tentons une connexion classique (vous pouvez trouver plus d'info sur Netcat sur un mini-tuto du site, voir les liens!) :
- Angel écoute et attends une connexion sur le port 10200:

fira@angel:~$ nc -l 10200

- Hihix essaie de s'y connecter:

fira@hihix:~$ nc 62.210.180.233 10200
(UNKNOWN) [62.210.180.233] 10200 (?) : Connection timed out
fira@hihix:~$

Aucun effet. C'est le cas car le NAT ne sait pas vers qui rediriger la connexion.

Essayons maintenant une connexion simultanée. Nous allons croiser les ports sources et destination pour bien connecter l'un à l'autre:

fira@hihix:~$ nc 62.210.180.233 8888 -p 8889
fira@hihix:~$ echo $?
1

Oups! Qu'est ce qu'il s'est passé? Netcat a retourné immédiatement avec un code d'erreur.
C'est le cas car le firewall devant angel est configuré pour rejeter les paquets inconnus (règle netfilter REJECT) : netcat reçoit cette erreur dès le début, et n'insiste pas.
Le firewall du Club*Nix, lui, ignore complétement ces paquets (règle netfilter DROP), donc Netcat continuera d'essayer de se connecter... Essayons dans ce sens:

root@angel:~# nc 147.215.81.100 8889 -p 8888 
wah! ca marche!
fira@hihix:~$ nc 62.210.180.233 8888 -p 8889
wah! ca marche!

Ca fonctionne: ce qui est entré d'un côté apparaît sur la machine distante.

Pourquoi ce n'est pas toujours si simple

Le problème dans ce cas très simple, c'est qu'on a fait une supposition qui est vraie pour le NAT Linux, mais pas toujours: que le NAT conserve les ports de connexion.
Nous avons admis que le port 8888 de hihix serait bien le même après passage du NAT, et inversement pour angel et le port 8889.
En pratique ce n'est pas forcément le cas: ce port pourrait déjà être utilisé, ou bien peut être que le NAT assigne ces ports aléatoirement...

Une implémentation complète peut prendre en compte de deviner et se rappeler de la méthode d'assignation du NAT.
Il est simple d'ensuite communiquer cette information à la personne voulant se connecter.
L'autre façon est de compter sur la ré-attribution des ports: une fois une connexion de test faite au serveur de rencontre,
il peut communiquer le port reçu et tenter de le réutiliser pour la connexion peer-to-peer.

Ca marche pour TCP, OK. Et UDP ?

Le même genre de schémas fonctionne pour UDP aussi. C'est même presque plus simple, car il n'y a pas d'établissement de session à proprement parler !
Une fois que le NAT a "appris" l'état en établissant la connexion des deux côtés, tout devrait fonctionner sans problème.

Conclusion

Voilà! J'espère que ça vous aura aidé à comprendre comment faire pour que vos applis cohabitent avec les NAT qu'on trouve un peu partout.
Merci d'avoir lu cet article et n'hésitez pas a laisser des commentaires pour plus d'infos :)

Liens et ressources

RFC 3022 décrivant le NAT
La page anglaise du Hole Punching TCP sur Wikipedia
La page sur le hole punching UDP, moins détaillée mais avec un exemple
Explication de l'implémentation utilisée dans BitTorrent
Le Hole Punching arbitraire à travers UPnP et ses dangers
Introduction a TCP/IP et Netcat sur le site du Club*Nix

Commentaires

Portrait de paul

Très intéressant !

Portrait de kinder

Voilà une article très intéressant. Je ne savais pas qu'en croisant les ports sur la commande NC et sur le FW, on pouvait établir la connexion derrière un NAT.

Concernant l'UPnP, le point important à souligner est qu'il est activé par défaut sur les box des FAI et que la fonction désactiver de ces même box est parfois non fonctionnelle.