La boîte à Tutoriels de Christopher PECAUD

Créer un scanner réseau en utilisant Python

Laisser un commentaire

SOMMAIRE

I Introduction
II Utilisation du module socket
III Utilisation de gethostbyaddr avec un ensemble de threads
IV Code utilisant un objet de type thread associé à un objet de type Queue
IV Conclusion

I Introduction

Dans ce document je vais vous montrer comment réaliser un scanner d’adresse IP en utilisant Python pour pouvoir référencer toutes les machines qui sont connectées sur votre réseau local à un instant donné. Pour ce faire nous nous appuierons sur les modules socket et threading. Nous verrons dans un premier temps une approche un peu simpliste qui est architecturée autour d’un scanner séquentiel mais qui donne des performances très mauvaises en termes de temps. Puis dans un second temps nous partirons sur une approche différente fondée sur l’utilisation de threads donnant de bien meilleures performances.

II Utilisation du module socket

Dans cet exemple nous allons découvrir comment récupérer les noms d’hôtes, les adresses IP, ainsi que les alias réseau des machines connectées à un réseau local. Pour ce faire on utilisera le module socket de python qui fournit des fonctions très importantes pour récupérer ce genre d’informations. La fonction qui nous intéresse dans ce tutoriel est : gethostbyaddr.

Elle prend en paramètre une adresse IP qu’on va définir auparavant et elle retourne les éléments suivants : les adresses IP, l’alias de la machine et le nom d’hôte. C’est principalement cette dernière information qui nous intéresse. L’adresse IP sera fournie en dur.

Donc dans un premier temps il faut importer le module socket :

Import socket

Puis on crée une boucle permettant de scanner une plage d’adresse IP. Imaginons que nous voulions scanner la plage 192.168.[1 – 254]. Voici la boucle que nous allons créer :

for ping in range(1,254):
     address = "192.168.1." + str(ping)
      ….

A l’intérieur de cette boucle nous allons utiliser la fonction que nous venons de voir, mais nous allons l’intégrer dans une structure try … except pour gérer l’exception levée lorsque aucun périphérique n’est connecté à l’adresse IP connecté.

On récupère les informations hostname, alias et addresslist avec cette ligne de code :

hostname, alias, addresslist = socket.gethostbyaddr(address)

Si aucune machine n’est présente à l’adresse scannée on leur attribut la valeur None à chaque variable exceptée addresslist :

hostname = None
alias = None
addresslist = address

Voici le code correspondant :


import socket
socket.setdefaulttimeout(1)
    try:
        hostname, alias, addresslist = socket.gethostbyaddr(address)

    except socket.herror:
        hostname = None
        alias = None
        addresslist = address

    print(addresslist, '=>', hostname)

Voici le code complet :


import socket

for ping in range(1,254)
     address = "192.168.1." + str(ping)
     socket.setdefaulttimeout(1)

    try:
        hostname, alias, addresslist = socket.gethostbyaddr(address)

    except socket.herror:
        hostname = None
        alias = None
        addresslist = address

    print(addresslist, '=>', hostname)

Ce code est correct mais si vous exécutez ce script, vous allez vous apercevoir que plus la plage d’adresse IP plus le processus de scan est long car toutes les recherches se font après complétion de l’ancienne recherche ce qui prend énormément de temps.

III Utilisation de gethostbyaddr avec un ensemble de threads

L’exemple précédent fonctionne mais n’est pas très efficace en ce qui concerne le temps d’exécution. Nous allons nous appuyer sur un autre module qui va nous permettre d’exécuter en parallèles ces tâches par l’intermédiaire de threads. Ce module s’appelle threading. Nous allons créer une classe qui hérite de la classe de base Thread qui prendra en charge l’exécution de la fonction gethostbyaddr.

import socket
import threading

Ce script s’appuiera sur une variable globale pour restituer les résultats liés à la recherche :

Hosts = {}

Nous définissons ensuite une classe permettant de créer notre objet Thread qui exécutera chacune des tâches de reconnaissance d’hôte sur notre réseau local. Elle hérite de la classe parente Thread

Class NetscanThread(threading.Thread) :


        ….

Dans le constructeur de la classe, nous allons définir et assigner la variable de classe address avec l’adresse IP que l’on enverra en paramètre lors de la construction de chaque objet Thread. Ce qui donne :

def __init__(self, address):

        self.address = address


        threading.Thread.__init__(self)  

Pour qu’un thread puisse fonctionner il faut que nous définissions au moins une méthode de classe qui s’appelle run qui exécutera la méthode de classe lookup définie à l’intérieur de la classe.

def run(self):


        self.lookup(self.address)

Voici maintenant la definition de la méthode lookup :

def lookup(self, address):
        """ On gère l'exception en cas de périphérique non connecté à l'adresse IP à scanner """
        try:

            """ On récupère le hostname et l'alias de la machine connectée """
            hostname, alias, _ = socket.gethostbyaddr(address)
           global host

            """ On associe le hostname à l'adresse IP et on les sauve dans le dictionnaire """
            host[address] = hostname

        except socket.herror:
            host[address] = None

Elle reprend exactement le même code que nous avions défini dans la première partie. A savoir que la recherche de l’hôte est assurée par la fonction gethostbyaddr définie dans le module socket. Celle-ci étant toujours encapsulée à l’intérieur d’un code de gestion de l’exception levée lorsqu’aucun périphérique réseau n’est présent à l’adresse IP indiquée.

La seule chose qui change est que nous attribuons à notre liste hosts globale la valeur hostname trouvée associée à sa clé qui n’est autre que l’adresse IP recherchée.

La définition de la classe est maintenant terminée. Passons au code du programme principal.

Dans cette partie nous allons définir la plage d’adresse IP que l’on souhaite scanner. Pour ce faire on crée une boucle avec un ensemble de valeurs allant de 1 à 254 pour que l’on puisse avoir un ensemble d’adresses IP allant de 192.168.0.1 à 192.168.0.254 :

 """ programme principal """


if __name__ == '__main__':

    addresses = []


    """ On définit une plage d'adresses IP à scanner """ 


    for ping in range(1, 254):


        addresses.append("192.168.0." + str(ping))

On crée ensuite une liste permettant de stocker les threads que l’on aura créés :

    threads = []

Pour chaque adresse IP créée on crée un thread de recherche d’hôte comme ceci :

netscanthreads = [NetscanThread(address) for address in addresses]

Voici le code complet :

import socket
import threading

""" Dictionnaire permettant de recueillir les hosnames des machines connectées
    sur un réseau """

host = {}
""" classe définissant le thread de scan d'adresse Ip servant à récupérer """
""" le hostname du périphérique réseau                                    """

class NetscanThread(threading.Thread):

    """ Constructeur de la classe prend en argument les paramètres suivants: """
    """ address : adresse IP à scanner                                       """
    def __init__(self, address):

        self.address = address
        threading.Thread.__init__(self)

    """ Définition de la méthode Run de notre classe de scan """
    def run(self):
        self.lookup(self.address)

    """ Méthode de classe permettant de récupérer le hostname du périphérique           """
    """ connecté au réseau. Elle prend en paramètrre la variable de classe représentant """
    """ l'adresse IP à recherchée                                                       """
    def lookup(self, address):

        """ On gère l'exception en cas de périphérique non connecté à l'adresse IP à scanner """
        try:
            """ On récupère le hostname et l'alias de la machine connectée """
            hostname, alias, _ = socket.gethostbyaddr(address)
            global host
            """ On associe le hostname à l'adresse IP et on les sauve dans le dictionnaire """
            host[address] = hostname
        except socket.herror:
            host[address] = None

""" programme principal """
if __name__ == '__main__':
    addresses = []

    """ On définit une plage d'adresses IP à scanner """ 
    for ping in range(1, 254):
        addresses.append("192.168.0." + str(ping))

    threads = []

    """ On créée autant de threads qu'il y à d'adresses IP à scanner """ 
    netscanthreads = [NetscanThread(address) for address in addresses] 
    for thread in netscanthreads :
        """ Chaque thread est démarré en même temps """
        thread.start()
        threads.append(thread)

    for t in threads:
        t.join()

    """ On affiche le résultat qui affiche pour chaque machine connectée son nom d'hôte """
    for address, hostname in host.items():
        if (hostname != None): 
            print(address, '=>', hostname)

IV Code utilisant un objet de type thread associé à un objet de type Queue

Dans cette partie nous allons partir de la même fonction que nous avons utilisée jusqu’à présent mais nous allons créer un objet de type Thread directement sans passer par la création d’une nouvelle classe.

Pour alimenter nos threads avec l’adresse IP à rechercher nous allons faire appel à un objet de type Queue (File Syncronisée). C’est par l’intermédiaire de cet objet que nous allons récupérer les noms d’hôtes découverts lors du scan. En effet ce type d’objet est très utile dans le multithreading pour l’échange de données.

Dans la section des imports nous allons importer le module queue en plus des deux autres vus précédemment.

import socket


from threading import Thread 


import queue

Ensuite nous définissons la fonction qui sera appelée par chaque objet Thread créé et qui va nous permettre de découvrir ou non le nom du périphérique associé à l’adresse IP envoyée en argument. La fonction prend en argument de même l’instance de l’objet File Synchronisée qui sera créée dans la suite de ce programme ainsi que le dictionnaire hostnames que nous définirons plus tard dans le programme. Le corps de la fonction est sensiblement le même que dans les autres exemples que nous venons de voir à l’exception de la dernière ligne qui va permettre de stocker les hostnames du périphérique dans l’objet « File Synchronisée » associés à chaque adresse IP en utilisant la méthode put() de la classe Queue. Voici la définition complète de la fonction :




def gethostname(address, q, hostnames):
    """ Gestion de l'exception au cas où un hôte ne répond pas à la demande """
    try:
        """ Appel de la fonction permettant de récupérer le nom d'hôte les alias """
        """    et les adresses IP de l'hôte                                      """
        hostname, alias, _ = socket.gethostbyaddr(address)

    except socket.herror:
        """ Aucun périphérique trouvé à l'adresse donnée en arguments """
        hostname = None
        alias = None
        addresslist = address

    """ On remplit le tableau avec les données récupérées """
    hostnames[address] = hostname

    """ On stocke le résultat dans l'objet Queue """
    q.put(hostnames)  

Nous passons ensuite au programme principal et nous commençons par créer notre instance d’objet Queue :

q = queue.Queue()

Comme précédemment nous créons notre liste de threads qui servira à stocker l’ensemble de nos threads créés pour pouvoir effectuer quelques traitements dessus, ainsi que notre dictionnaire qui stockera les noms d’hôtes en fonction de l’adresse IP recherchée.

""" Tableau qui stockera les threads créés """
threads = []

""" dictionnaire stockant les noms d'hôte récupérés """

hostnames = {}

Comme dans les exemples précédents nous créons notre plage d’adresses IP et pour chaque adresse IP créée nous créons une instance d’objet de type Thread. Nous appelons le constructeur de la classe Thread en lui envoyant comme source la fonction de recherche de noms d’hôte. Viennent ensuite la définition des arguments de la fonction : address, q et le dictionnaire hostnames que nous venons de définir. On stocke dans notre liste chaque thread nouvellement créé.

""" On définit la plage d'adresse IP à scruter """

for ping in range(1,254):


   """ Variable permettant de stocker l'adresse IP courante à scruter """
   address = "192.168.0." + str(ping)
    """ Création du thread avec appel de la fonction gethostname pour traitement de l'adresse IP """
    t = Thread(target=gethostname, args=(address,q, hostnames))

    """ On ajoute chaque thread dans le tableau """

    threads.append(t)

Nous démarrons ensuite les threads créés :

for t in threads:


    t.start()

Et nous les associons de sorte qu’il faudra attendre la fin de tous les threads pour obtenir notre liste de noms d’hôte :

""" On unit tous les threads pour être sûr que tous ceux-ci renvoient leur valeur """

for t in threads:


    t.join()

Et on récupère le résultat contenu dans notre objet Queue en utilisant la méthode get() de la classe :

"" On récupère les valeurs stockées dans la file d’attente de threads et on les stocke dans le dictionnaire """

hostnames = q.get()

Et on affiche le résultat comme précédemment :

""" On itère chaque paire de clé/valeur à la recherche des noms d'hôte """
for address, hostname in hostnames.items():
    if (hostname != None):
        print(address, '=>', hostname)

Voici le code complet du programme :

import socket
from threading import Thread 
import queue

""" fonction appelé par le thread pour la recherche de l'hôte correspondant à une adresse """
""" IP donnée                                                                             """
""" Prend en argument l'adresse IP et l'instance de l'objet Queue créée dans le programme """
""" pricipal                                                                              """
def gethostname(address, q, hostnames):
    """ Gestion de l'exception au cas où un hôte ne répond pas à la demande """
    try:
        """ Appel de la fonction permettant de récupérer le nom d'hôte les alias """
        """    et les adresses IP de l'hôte                                      """
        hostname, alias, _ = socket.gethostbyaddr(address)
    except socket.herror:
       """ Aucun périphérique trouvé à l'adresse donnée en arguments """
        hostname = None
        alias = None
        addresslist = address
    """ On remplit le tableau avec les données récupérées """
    hostnames[address] = hostname
    """ On stocke le résultat dans l'objet Queue """

    q.put(hostnames)     

""" Programme princupal """
""" Création de l'objet Queue """
q = queue.Queue()
""" Tableau qui stockera les threads créés """
threads = []

""" dictionnaire stockant les noms d'hôte récupérés """ 
hostnames = {}

""" On défini la plage d'adresse IP à scruter """
for ping in range(1,254):
    """ Variable permettant de stocké l'adresse IP courante à scruter """
    address = "192.168.0." + str(ping)
    """ Création du thread avec appel de la fonction gethostname pour traitement de l'adresse IP """
    t = Thread(target=gethostname, args=(address,q, hostnames))

    """ On ajoute chaque thread dans le tableau """
    threads.append(t)

""" On démarre tous les threads créés """
for t in threads:
    t.start()
""" On unit tous les threads pour être sûr que tous ceux-ci renvoient leur valeur """
for t in threads:
    t.join()
""" On récupère les valeurs stockées dans la queue de threads et on les stocke dans le dictionnaire """
hostnames = q.get()
""" On itère chaque paire de clé/valeur à la recherche des noms d'hôte """
for address, hostname in hostnames.items():
    if (hostname != None):
        print(address, '=>', hostname)

V Conclusion

Nous avons vu que plusieurs techniques pouvaient être utilisé pour rechercher des informations sur nos hôtes sur notre réseau local. Cependant pour que ce processus soit efficace il faut utiliser un certain nombre de techniques qui permettent de paralléliser les traitements comme les threads. La problématique de l’utilisation des threads concerne le traitement des données. Nous avons vu que nous pouvions avoir recours à une variable globale. Mais le dernier exemple semble être le plus adéquate.

blog comments powered by Disqus