Serveur-client
L’objectif de ce billet est de parvenir à nous connecter à un serveur a priori inaccessible derrière un NAT.
Client-serveur
De nos jours, TCP est toujours utilisé en mode client-serveur :
- le serveur écoute passivement sur un port donné, en attente de la connexion d’un client ;
- le client initie activement une connexion vers un serveur.
Une fois la connexion établie, cependant, le client et le serveur jouent exactement le même rôle au niveau de la communication. Par contre, très souvent, leur rôle applicatif dépend directement de celui qui a initié la connexion :
- c’est le client HTTP qui va envoyer une requête au serveur HTTP, pas l’inverse ;
- c’est le client SSH qui va ouvrir une session sur le serveur SSH…
Ce fonctionnement paraît tellement naturel que “client” désigne bien souvent à la fois celui qui initie la connexion et celui qui effectue des requêtes (au serveur), alors que “serveur” désigne aussi bien la partie en écoute que celle qui répondra aux requêtes (des clients).
Puis vint le NAT…
Avec la pénurie d’adresses IPv4, le NAT s’est généralisé. Bien souvent, un accès internet ne fournit qu’une seule adresse IPv4. Les différents ordinateurs partageant la même connexion ne sont alors pas accessibles directement depuis l’extérieur (il est nécessaire d’ouvrir des ports).
Ainsi, derrière un NAT sans ports ouverts, un serveur ne sera pas accessible publiquement. Par contre, un client pourra continuer à se connecter à n’importe quel serveur public.
Inversion des rôles
Il existe des situations pour lesquelles nous souhaitons qu’un logiciel joue le rôle de serveur au niveau applicatif, afin de répondre aux requêtes des clients, mais client au niveau de la communication, afin de passer les NATs sans difficultés.
Par exemple, nous pouvons vouloir accéder, grâce à VNC ou SSH, à un ordinateur se trouvant derrière un NAT sur lequel, par hypothèse, nous n’avons pas la main. Dans ce cas, seul le serveur (au sens applicatif) aura la capacité d’ouvrir une connexion vers le client.
Logiciel dédié
Il est possible d’utiliser un logiciel spécialement conçu pour gérer cette inversion des rôles. C’est le cas par exemple de gitso, qui inverse le protocole VNC afin de simplifier l’aide de novices à distance.
Cette solution a cependant l’inconvénient d’être très spécifique, nécessitant un développement supplémentaire pour chaque protocole.
Redirection de port distant via SSH
SSH permet d’ouvrir un tunnel pour rediriger un port d’une machine distance vers une adresse quelconque.
Par exemple, après avoir démarré la redirection :
ssh un_serveur_public -NR2222:localhost:22
toutes les connexions arrivant sur un_serveur_public:2222
seront redirigées de
manière transparente vers localhost:22
(sur la machine ayant initié le tunnel,
donc).
(Cela nécessite d’activer GatewayPorts yes
dans /etc/ssh/sshd_config
sur
un_serveur_public
.)
De cette manière, un serveur SSH inaccessible derrière un NAT est rendu
accessible à travers un tunnel en passant par une machine publique
(un_serveur_public
). Ainsi, il est possible de s’y connecter avec la
commande :
ssh un_serveur_public -p2222
Cette stratégie fonctionne bien, mais elle nécessite que la machine qui souhaite
exposer un serveur grâce à un tunnel possède un accès SSH sur
un_serveur_public
.
Si l’on souhaite aider quelqu’un grâce à la prise de contrôle de sa machine à distance, il y a toutes les chances que cette personne n’ait pas d’accès SSH vers une machine publiquement accessible. Il est alors possible de lui créer un compte restreint dédié sur un serveur que l’on contrôle, mais c’est très intrusif, et il faut s’assurer de ne pas réduire la sécurité.
Mais en fait, cette contrainte est superflue.
Redirections SOCAT
La redirection de port distant nécessite des permissions car, outre le fait qu’elle est implémentée sur SSH, il serait déraisonnable d’autoriser n’importe qui à ouvrir une socket en écoute sur un port arbitraire d’une machine distante.
Pour éviter ce problème, nous pouvons décomposer la redirection de port distant fourni par SSH en deux parties :
- l’ouverture de la connexion vers
un_serveur_public
, redirigée vers l’adresselocalhost:22
dans l’exemple précédent ; - l’ouverture d’une socket en écoute sur un port (
2222
) de la machine distante, redirigée vers la première connexion.
L’idée est de mettre en place le premier demi-tunnel sur la machine serveur, et le second demi-tunnel, nécessitant des permissions, sur la machine publique, contrôlée par le client.
Pour cela, nous allons utiliser l’outil socat
, qui permet de relayer les
données entre deux sockets, quelque soit le rôle qu’elles aient joué lors de
l’initialisation.
Active-passive
Pour comprendre son utilisation, nous allons ouvrir grâce à netcat (nc
) une
socket TCP en écoute sur le port 5000
et nous y connecter :
Toute entrée validée par un retour à la ligne dans le terminal 1 s’affichera dans le terminal 2 (et vice-versa).
Passive-passive
Démarrons maintenant dans deux terminaux différents une socket en écoute sur les
ports 1111
et 2222
:
Pour les mettre en communication avec socat
, dans un 3e terminal :
socat tcp:localhost:1111 tcp:localhost:2222
Active-active
Inversement, il est possible de mettre en communication deux sockets actives (sans compter sur leur synchronisation). Pour cela, commençons par ouvrir le serveur relai :
socat tcp-listen:1111 tcp-listen:2222
Puis connectons-y deux sockets :
Tunnel
Nous sommes maintenant prêts pour créer l’équivalent d’une redirection de port
distant SSH grâce à deux socat
s, qui vont permettre d’inverser la connexion
uniquement sur la portion qui permet de traverser le NAT :