Comment vérifier l'accessibilité d'un port TCP en Python (Synchrone & Asynchrone)

Comment vérifier l’accessibilité d’un port TCP en Python (Synchrone & Asynchrone)

Table des matières

Rien ne bloque un déploiement plus vite qu’une erreur « Connection Refused » que vous n’avez pas vue venir. Que vous déboguiez un changement silencieux de firewall ou que vous supervisiez un microservice, vérifier l’accessibilité TCP est la première ligne de défense du dépannage réseau.

Une méthode courante consiste à vérifier si un service TCP sur un serveur distant accepte les connexions.

Dans cet article, nous allons voir comment implémenter une vérification simple de l’accessibilité d’un service TCP distant en Python, en utilisant des approches synchrones et asynchrones.

À la fin, vous saurez comment créer ces vérifications, quand les utiliser, et pourquoi elles sont essentielles.

Pourquoi vérifier l’accessibilité TCP ?

Concrètement, vérifier l’accessibilité TCP consiste à tenter d’établir une connexion vers un port précis sur un serveur distant. Si la connexion réussit, le service est accessible. Si elle échoue (timeout, connexion refusée, etc.), le service est indisponible ou inaccessible.

C’est utile pour :

  • Superviser la disponibilité des services.
  • Vérifier des configurations réseau ou firewall.
  • Déboguer des problèmes de connexion dans des systèmes distribués.

Si vous utilisez un terminal Linux dans un conteneur restreint, vous pouvez également tester vos ports via les fonctionnalités natives de Bash. Consultez mon guide sur les 5 commandes essentielles de débogage réseau sous Linux minimal pour une solution rapide et sans installation.

Voyons maintenant comment l’implémenter sans dépendre d’outils ou de bibliothèques tierces.

Sans outils ou bibliothèques tierces

En Python, la bibliothèque standard socket fournit tout le nécessaire pour vérifier l’accessibilité TCP. Nous allons voir deux approches : synchrone (bloquante) et asynchrone (non bloquante).

Implémentation synchrone

L’approche synchrone est simple et facile à comprendre. Elle tente de créer une connexion vers le service distant et réussit ou échoue après un délai.

Voici le code :

 1import socket
 2
 3def check_port_reachable(address: str, port: int, timeout_sec: float = 3) -> bool:
 4    """
 5    Vérifie que le couple (adresse, port) est accessible via une connexion TCP.
 6    Retourne True si accessible, sinon False.
 7
 8    Parameters
 9    ----------
10    address : str
11        Adresse IP ou nom d'hôte du service distant.
12    port : int
13        Numéro du port à vérifier.
14    timeout_sec : int
15        Délai maximal de la tentative de connexion, en secondes.
16
17    Returns
18    ------
19    bool
20        True si le port est accessible, False sinon.
21    """
22    # Pour IPv6, utiliser `AF_INET6` au lieu de `AF_INET`
23    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
24    sock.settimeout(timeout_sec)
25
26    try:
27        sock.connect((address, port))
28        sock.shutdown(socket.SHUT_RDWR)
29        sock.close()
30        return True
31    except (socket.timeout, socket.error):
32        sock.close()
33        return False

Fonctionnement

  1. Un socket TCP est créé via socket.socket.
  2. La méthode connect tente de se connecter à (adresse, port) dans le délai timeout_sec.
  3. En cas de succès, la connexion est fermée proprement et la fonction retourne True. En cas d’échec, elle retourne False.

Version alternative avec connect_ex

Une légère variante utilise connect_ex, qui retourne 0 si la connexion réussit, ou un code d’erreur sinon :

 1def check_port_with_connect_ex(address: str, port: int, timeout_sec: float = 3) -> bool:
 2    try:
 3         # Pour IPv6, utiliser `AF_INET6` au lieu de `AF_INET`
 4        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
 5            sock.settimeout(timeout_sec)
 6            if sock.connect_ex((address, port)) == 0:
 7                return True
 8        return False
 9    except OSError:
10        return False

Cette version gère plus proprement les erreurs (IP invalide, port incorrect) et évite de capturer des exceptions trop génériques.

Tip

Si vous comparez cette version avec check_port_reachable, vous verrez que j’utilise l’instruction with lors de l’ouverture du socket.

Cela garantit la fermeture automatique du socket à la sortie du bloc with.

Implémentation asynchrone

Pour les applications nécessitant des entrées/sorties non bloquantes, notamment lorsqu’il faut gérer de nombreuses connexions, l’approche asynchrone avec asyncio est plus efficace.

Voici une implémentation asynchrone :

 1import asyncio
 2
 3async def check_port_reachable_async(address, port, timeout_sec=3):
 4    """
 5    Vérifie de manière asynchrone si le couple (adresse, port) est accessible via TCP.
 6    Retourne True si accessible, sinon False.
 7
 8    Parameters
 9    ----------
10    address : str
11        Adresse IP ou nom d'hôte du service distant.
12    port : int
13        Numéro du port à vérifier.
14    timeout_sec : int
15        Délai maximal de la tentative de connexion, en secondes.
16
17    Returns
18    ------
19    bool
20        True si le port est accessible, False sinon.
21    """
22    writer = None
23    try:
24        # Tentative de connexion asynchrone
25        reader, writer = await asyncio.wait_for(
26            asyncio.open_connection(address, port),
27            timeout=timeout_sec
28        )
29        # Fermeture propre de la connexion
30        writer.close()
31        await writer.wait_closed()
32        return True
33    except (TimeoutError, asyncio.TimeoutError, ConnectionRefusedError, OSError):
34        return False
35    finally:
36        if writer:
37            writer.close()
38            try:
39                await writer.wait_closed() # ajouté en Python 3.7
40            except (OSError, AttributeError): 
41                pass # Déjà fermé

Fonctionnement

  1. asyncio.open_connection tente d’établir une connexion TCP de façon asynchrone.
  2. En cas de succès, la connexion est fermée et True est retourné.
  3. En cas de timeout ou de refus, la fonction retourne False.

Pourquoi utiliser l’asynchrone ?

Si vous devez vérifier plusieurs services en parallèle, l’asynchrone est préférable. Plutôt que d’attendre chaque connexion l’une après l’autre, asyncio permet de lancer toutes les vérifications simultanément.

Les gains de performance sont importants pour la supervision de clusters ou de systèmes distribués.

Quand utiliser un mécanisme de retry avec backoff

Des problèmes réseau temporaires peuvent provoquer des échecs ponctuels. Dans ces cas, il est utile de réessayer la vérification plusieurs fois avant d’abandonner. On peut utiliser une boucle simple.

Pour des tâches simples, une boucle suffit. En production, il faut aller plus loin. Des stratégies comme le backoff exponentiel et le jitter évitent de surcharger les systèmes.

Voici un exemple simple avec délai constant :

  • Synchrone
  • Asynchrone
1import time
2
3def check_with_retries(address, port, retries=3, delay_sec=2):
4    for attempt in range(retries):
5        if check_port_reachable(address, port):
6            return True
7        time.sleep(delay_sec)
8    return False
1import asyncio
2
3async def check_with_retries_async(address, port, retries=3, delay_sec=2):
4    for attempt in range(retries):
5        if await check_port_reachable_async(address, port):
6            return True
7        await asyncio.sleep(delay_sec)
8    return False

Cette fonction tente la vérification jusqu’à 3 fois, avec 2 secondes d’attente entre chaque essai.

Causes courantes d’inaccessibilité

Plusieurs facteurs peuvent rendre un service distant inaccessible :

  • Service arrêté : le service ne tourne pas sur la machine distante.
  • Problèmes réseau : routage, pertes de paquets, DNS défaillant.
  • Adresse ou port incorrect : vérifiez l’IP/le nom d’hôte et le port.
  • Restrictions firewall : les connexions entrantes ou sortantes sont bloquées.
  • Blocage silencieux des paquets : certains firewalls configurés en mode DROP ne renvoient pas de refus. Le script attend alors jusqu’à la fin du timeout_sec avant d’échouer.

Comprendre la raison de l’échec est essentiel pour le corriger. Est-ce une erreur temporaire ou une panne complète du système ? Je détaille ces différences clés dans l’Explication illustrée des notions de Fault, Error, Failure, Bug et Defect en logiciel.

Utilisation d’outils et bibliothèques tierces

La bibliothèque standard de Python est puissante, mais les outils tiers simplifient les vérifications complexes ou les besoins de scan réseau.

Ils offrent souvent des fonctionnalités avancées comme l’identification de services, le multithreading, ou des mécanismes de retry plus robustes.

Voici une sélection de bibliothèques populaires, suivie d’un tableau comparatif.

Outils et bibliothèques populaires

  • Scapy : bibliothèque puissante de manipulation de paquets réseau. Adaptée au scan de ports et à l’analyse de protocoles.

  • PortScan : bibliothèque Python légère pour scanner des ports et services distants. Simple et rapide.

  • TPScanner : scanner de ports TCP en Python, facilement adaptable à plusieurs ports.

  • python-nmap : wrapper Python de Nmap. Permet le scan de ports, la détection de services et l’empreinte OS.

  • PyPortScanner : bibliothèque simple pour vérifier rapidement l’ouverture de ports TCP.

Récapitulatif des bibliothèques tierces

Bibliothèque Fonctionnalités Complexité d’installation Cas d’usage idéal
Scapy Manipulation de paquets, scan, tests de sécurité Moyenne Tests réseau avancés
PortScan Scan TCP basique, léger, rapide Faible Vérifications simples et rapides
TPScanner Scan multi-ports, adaptable, open-source Faible Scan léger de plusieurs ports
python-nmap Scan réseau complet (services, OS, etc.) Moyenne (Nmap requis) Analyse réseau approfondie
PyPortScanner Scan TCP simple et léger Faible Vérifications rapides de ports

Ces bibliothèques permettent de choisir l’outil le plus adapté selon vos besoins, du simple test de port à l’analyse réseau avancée.

Foire aux questions

Quelle est la différence entre un ping et une vérification TCP ?
Le ping utilise ICMP pour vérifier si une machine est joignable, mais il ne dit rien sur un service précis. Une vérification TCP confirme qu’un port spécifique écoute réellement et accepte des connexions.
Est-ce que la vérification ouvre réellement une session ?
Oui. Ces méthodes réalisent un « three-way handshake ». La connexion est immédiatement fermée afin de limiter l’impact sur le service distant. Techniquement, socket.connect effectue un three-way handshake complèt sans envoyer de données applicatives, ce qui reste très léger et sûr pour une supervision fréquente.

Conclusion

Vérifier l’accessibilité d’un service TCP distant est une tâche essentielle dans de nombreux contextes réseau. Grâce aux bibliothèques intégrées de Python, il est facile d’implémenter des approches synchrones et asynchrones.

Selon votre cas d’usage, vous pouvez ajouter des mécanismes de retry ou utiliser des outils tiers pour simplifier le processus.

Ces techniques facilitent la gestion des serveurs et le débogage des problèmes de connectivité. Elles constituent la première étape pour garantir que vos applications restent accessibles et fonctionnelles.

Articles Connexes

`1ms` : La Fausse Promesse de `sleep()` pour le Code Temps Réel — Résolution et Jitter Réparées

1ms : La Fausse Promesse de sleep() pour le Code Temps Réel — Résolution et Jitter Réparées

Si vous avez déjà créé un système en temps réel, un simulateur de données ou une boucle de jeu, vous avez probablement essayé d’utiliser sleep() …

Lire la suite
Qu'est-ce qu'une variable en programmation ? Guide simple pour débutants

Qu’est-ce qu’une variable en programmation ? Guide simple pour débutants

Comment les programmes stockent-ils et manipulent-ils des informations ? La réponse se trouve dans les variables, les éléments essentiels de la …

Lire la suite
Enums vs Constantes : Pourquoi les Enums sont une solution plus sûre et plus intelligente

Enums vs Constantes : Pourquoi les Enums sont une solution plus sûre et plus intelligente

Utilisez-vous toujours des entiers ou des chaînes de caractères pour représenter des catégories fixes dans votre code ? Si c’est le cas, vous …

Lire la suite