Serveur DHCP+Mysql en Python

Serveur DHCP+Mysql en Python

L'objectif de ce projet était :

  • Étude du protocole DHCP lors du travail sur le réseau IPv4
  • Apprentissage de Python (un peu plus qu'à partir de zéro 😉 )
  • remplacement du serveur DB2DHCP (mon fork), l'original ici, qui devient de plus en plus difficile à compiler pour le nouveau système d'exploitation. Et je n'aime pas que le binaire, que l'on ne peut pas « changer tout de suite »
  • obtention d'un serveur DHCP opérationnel avec la possibilité de sélectionner l'adresse IP de l'abonné en fonction de l'adresse MAC de l'abonné ou de la combinaison adresse MAC switch + port (Option 82)
  • écriture d'un énième bicyle (Oh! c'est mon activité préférée)
  • recevoir des leçons sur ma maladresse sur Habr Habr (ou mieux un invitation) 😉

Résultat : ça fonctionne 😉 Testé sur FreeBSD et Ubuntu. Théoriquement, le code peut être demandé pour fonctionner sur n'importe quel système d'exploitation, car il ne semble pas y avoir d'attaches spécifiques dans le code.
Attention ! Il y a beaucoup à venir.

Lien vers le dépôt pour ceux qui aiment « toucher en direct ».

Le processus d'installation, de configuration et d'utilisation du résultat « étude de la matière » est bien en dessous, et ensuite un peu de théorie sur le protocole DHCP. Pour moi. Et pour l'histoire 😉

Un peu de théorie

Qu'est-ce que le DHCP

C'est un protocole réseau qui permet à un appareil de connaître son adresse IP (et d'autres paramètres comme la passerelle, DNS, etc.) à partir d'un serveur DHCP. L'échange de paquets se fait via le protocole UDP. Le principe général de fonctionnement de l'appareil lors de la demande de paramètres réseau est le suivant :

  1. L'appareil (client) envoie une requête UDP de diffusion (DHCPDISCOVER) sur tout le réseau demandant « eh bien, quelqu'un, donnez-moi une adresse IP ». En règle générale (mais pas toujours), la demande se fait depuis le port 68 (source), et la destination est le port 67 (destination). Certains appareils envoient des paquets même depuis le port 67. À l'intérieur du paquet DHCPDISCOVER, l'adresse MAC de l'appareil client est incluse.
  2. Tous les serveurs DHCP présents dans le réseau (et il peut y en avoir plusieurs) forment pour l'appareil ayant envoyé le DHCPDISCOVER, une offre DHCPOFFER avec les paramètres réseau, et l'envoient également par diffusion sur le réseau. L'identification de qui est destiné ce paquet se fait par l'adresse MAC du client fournie précédemment dans la demande DHCPDISCOVER.
  3. Le client reçoit des paquets d'offres de configurations réseau, choisit celle qui lui semble la plus attrayante (les critères peuvent varier, comme par exemple le temps de livraison du paquet, le nombre de routes intermédiaires), et fait une "demande officielle" DHCPREQUEST avec les configurations réseau au serveur DHCP qui lui plaît. Dans ce cas, le paquet est alors dirigé vers le serveur DHCP spécifique.
  4. Le serveur qui reçoit le DHCPREQUEST envoie un paquet au format DHCPACK, dans lequel il énumère à nouveau les configurations réseau destinées à ce client.

Serveur DHCP+Mysql en Python

De plus, il existe des paquets DHCPINFORM qui viennent du client et qui ont pour but d'informer le serveur DHCP que "le client est actif" et utilise les configurations réseau fournies. Dans cette implémentation de serveur, ces paquets sont ignorés.

Format des paquets

Dans l'ensemble, le cadre du paquet Ethernet ressemble à ceci :

Serveur DHCP+Mysql en Python

Dans notre cas, nous ne considérerons que les données contenues directement dans le paquet UDP, sans les en-têtes des protocoles des niveaux OSI, à savoir la structure DHCP :

DHCPDISCOVER

Ainsi, le processus d'obtention d'une adresse IP pour un appareil commence lorsque le client DHCP envoie une requête de diffusion depuis le port 68 vers 255.255.255.255:67. Dans ce paquet, le client inclut son adresse MAC ainsi que ce qu'il souhaite recevoir du serveur DHCP. La structure du paquet est décrite dans le tableau ci-dessous.

Tableau de structure du paquet DHCPDISCOVER

Position dans le paquet
Nom de la valeur
Exemple
Représentation
Octet
Explication

1
Boot Request
1
Hex
1
Type de message. 1 — demande du client au serveur, 2 — réponse du serveur au client

2
Type de matériel
1
Hex
1
Type d'adresse matérielle, dans ce protocole 1 — MAC

3
Longueur de l'adresse matérielle
6
Hex
1
Longueur de l'adresse MAC de l'appareil

4
Sauts
1
Hex
1
Nombre de routes intermédiaires

5
Identifiant de transaction
23:cf:de:1d
Hex
4
Identifiant unique de la transaction. Généré par le client au début de l'opération de demande

7
Secondes écoulées
0
Hex
4
Temps en secondes depuis le début du processus d'obtention de l'adresse

9
Bootp flags
0
Hex
2
Certains drapeaux qui peuvent être définis, en tant qu'indication des paramètres du protocole

11
Adresse IP du client
0.0.0.0
Chaîne
4
Adresse IP du client (si elle existe)

15
Votre adresse IP client
0.0.0.0
Chaîne
4
Adresse IP proposée par le serveur (si elle existe)

19
Adresse IP du serveur suivant
0.0.0.0
Chaîne
4
Adresse IP du serveur (si connue)

23
Adresse IP de l'agent de relais
172.16.114.41
Chaîne
4
Adresse IP de l'agent de relais (par exemple, un switch)

27
Adresse MAC du client
14:d6:4d:a7:c9:55
Hex
6
Adresse MAC de l'expéditeur du paquet (client)

31
Remplissage de l'adresse matérielle du client
 
Hex
10
Espace réservé. Généralement rempli de zéros

41
Nom d'hôte du serveur
 
Chaîne
64
Nom du serveur DHCP. Souvent non transmis

105
Nom du fichier d'amorçage
 
Chaîne
128
Nom de fichier sur le serveur utilisé par les stations sans disque lors du démarrage

235
Cookie magique
63:82:53:63
Hex
4
Le « nombre magique » permettant notamment de déterminer que ce paquet appartient au protocole DHCP

Options DHCP. Elles peuvent apparaître dans n'importe quel ordre

236
Numéro de l'option
53
Déc
1
Option 53, qui définit le type de paquet DHCP

1 — DHCPDISCOVER
3 — DHCPREQUEST
2 — DHCPOFFER
5 — DHCPACK
8 — DHCPINFORM

 
Longueur de l'option
1
Déc
1

 
Valeur de l'option
1
Déc
1

 
Numéro de l'option
50
Déc
1
Quelle adresse IP le client souhaite-t-il obtenir ?

 
Longueur de l'option
4
Déc
1

 
Valeur de l'option
172.16.134.61
Chaîne
4

 
Numéro de l'option
55
 
1
Paramètres réseau demandés par le client. Leur composition peut varier.

01 — Masque de réseau
03 — Passerelle
06 — DNS
oc — Nom d'hôte
0f — Nom de domaine réseau
1c — Adresse de diffusion (broadcast)
42 — Nom du serveur TFTP
79 — Route statique sans classe

 
Longueur de l'option
8
 
1

 
Valeur de l'option
01:03:06:0c:0f:1c:42:79
 
8

 
Numéro de l'option
82
Déc
 
Option 82, qui transmet l'adresse MAC de l'appareil — le répéteur et d'autres valeurs supplémentaires.

Le plus souvent — le port du switch sur lequel fonctionne le client final DHCP. Dans cette option, des paramètres supplémentaires sont « imbriqués ». Le premier octet est le numéro de la « sous-option », le deuxième en est la longueur, puis vient sa valeur.

Dans ce cas, dans l'option 82, des sous-options sont imbriquées :
Agent Circuit ID = 00:04:00:01:00:04, où les deux derniers octets représentent le port du client DHCP à partir duquel la demande est venue.

Agent Remote ID = 00:06:c8:be:19:93:11:48 — adresse MAC de l'appareil répéteur DHCP.

 
Longueur de l'option
18
Déc
 

 
Valeur de l'option
01:06
00:04:00:01:00:04
02:08
00:06:c8:be:19:93:11:48
Hex
 

 
Fin du paquet
255
Déc
1
255 symbolise la fin du paquet

DHCPOFFER

Dès que le serveur reçoit le paquet DHCPDISCOVER et s'il voit qu'il peut offrir quelque chose demandé au client, il forme une réponse — DHCPOFFER. La réponse est envoyée sur le port « d'où elle vient », en broadcast, car à ce moment, le client n'a pas encore d'adresse IP, donc il ne peut accepter le paquet que s'il est envoyé en mode broadcast. Le client reconnaît que c'est un paquet pour lui par son adresse MAC contenue dans le paquet, ainsi que par le numéro de transaction qu'il génère au moment de la création du premier paquet.

Table de structure du paquet DHCPOFFER

Position dans le paquet
Nom de la valeur (usage courant)
Exemple
Représentation
Octet
Explication

1
Boot Request
1
Hex
1
Type de message. 1 — demande du client au serveur, 2 — réponse du serveur au client

2
Type de matériel
1
Hex
1
Type d'adresse matérielle, dans ce protocole 1 — MAC

3
Longueur de l'adresse matérielle
6
Hex
1
Longueur de l'adresse MAC de l'appareil

4
Sauts
1
Hex
1
Nombre de routes intermédiaires

5
Identifiant de transaction
23:cf:de:1d
Hex
4
Identifiant unique de la transaction. Généré par le client au début de l'opération de demande

7
Secondes écoulées
0
Hex
4
Temps en secondes depuis le début du processus d'obtention de l'adresse

9
Bootp flags
0
Hex
2
Certains drapeaux qui peuvent être définis comme indication des paramètres du protocole. Dans ce cas, 0 — signifie un type de demande Unicast

11
Adresse IP du client
0.0.0.0
Chaîne
4
Adresse IP du client (si elle existe)

15
Votre adresse IP client
172.16.134.61
Chaîne
4
Adresse IP proposée par le serveur (si elle existe)

19
Adresse IP du serveur suivant
0.0.0.0
Chaîne
4
Adresse IP du serveur (si connue)

23
Adresse IP de l'agent de relais
172.16.114.41
Chaîne
4
Adresse IP de l'agent de relais (par exemple, un switch)

27
Adresse MAC du client
14:d6:4d:a7:c9:55
Hex
6
Adresse MAC de l'expéditeur du paquet (client)

31
Remplissage de l'adresse matérielle du client
 
Hex
10
Espace réservé. Généralement rempli de zéros

41
Nom d'hôte du serveur
 
Chaîne
64
Nom du serveur DHCP. Souvent non transmis

105
Nom du fichier d'amorçage
 
Chaîne
128
Nom de fichier sur le serveur utilisé par les stations sans disque lors du démarrage

235
Cookie magique
63:82:53:63
Hex
4
Le « nombre magique » permettant notamment de déterminer que ce paquet appartient au protocole DHCP

Options DHCP. Elles peuvent apparaître dans n'importe quel ordre

236
Numéro de l'option
53
Déc
1
Option 53, qui définit le type de paquet DHCP 2 — DHCPOFFER

 
Longueur de l'option
1
Déc
1

 
Valeur de l'option
2
Déc
1

 
Numéro de l'option
1
Déc
1
Option proposant au client DHCP un masque de réseau

 
Longueur de l'option
4
Déc
1

 
Valeur de l'option
255.255.224.0
Chaîne
4

 
Numéro de l'option
3
Déc
1
Option proposant au client DHCP une passerelle par défaut

 
Longueur de l'option
4
Déc
1

 
Valeur de l'option
172.16.12.1
Chaîne
4

 
Numéro de l'option
6
Déc
1
Option proposant au client DHCP un serveur DNS

 
Longueur de l'option
4
Déc
1

 
Valeur de l'option
8.8.8.8
Chaîne
4

 
Numéro de l'option
51
Déc
1
Durée de vie des paramètres réseau accordés en secondes, après quoi le client DHCP doit les demander à nouveau

 
Longueur de l'option
4
Déc
1

 
Valeur de l'option
86400
Déc
4

 
Numéro de l'option
82
Déc
1
Option 82, qui répète ce qui est venu dans le DHCPDISCOVER

 
Longueur de l'option
18
Déc
1

 
Valeur de l'option
01:08:00:06:00
01:01:00:00:01
02:06:00:03:0f
26:4d:ec
Déc
18

 
Fin du paquet
255
Déc
1
255 symbolise la fin du paquet

DHCPREQUEST

Après que le client ait reçu le DHCPOFFER, il forme un paquet avec une demande de paramètres réseau non plus à tous les serveurs DHCP du réseau, mais uniquement à un serveur spécifique dont l'offre DHCPOFFER lui a le plus "plu". Les critères de "plaisir" peuvent être divers et dépendent de l'implémentation du client DHCP. Le destinataire de la demande est indiqué par l'adresse MAC du serveur DHCP. De plus, le paquet DHCPREQUEST peut être envoyé par le client sans avoir d'abord formé un DHCPDISCOVER, si l'adresse IP du serveur a déjà été obtenue précédemment.

Table de structure du paquet DHCPREQUEST

Position dans le paquet
Nom de la valeur (usage courant)
Exemple
Représentation
Octet
Explication

1
Boot Request
1
Hex
1
Type de message. 1 — demande du client au serveur, 2 — réponse du serveur au client

2
Type de matériel
1
Hex
1
Type d'adresse matérielle, dans ce protocole 1 — MAC

3
Longueur de l'adresse matérielle
6
Hex
1
Longueur de l'adresse MAC de l'appareil

4
Sauts
1
Hex
1
Nombre de routes intermédiaires

5
Identifiant de transaction
23:cf:de:1d
Hex
4
Identifiant unique de la transaction. Généré par le client au début de l'opération de demande

7
Secondes écoulées
0
Hex
4
Temps en secondes depuis le début du processus d'obtention de l'adresse

9
Bootp flags
8000
Hex
2
Certains drapeaux qui peuvent être définis comme indication des paramètres du protocole. Dans ce cas, le "broadcast" est activé.

11
Adresse IP du client
0.0.0.0
Chaîne
4
Adresse IP du client (si elle existe)

15
Votre adresse IP client
172.16.134.61
Chaîne
4
Adresse IP proposée par le serveur (si elle existe)

19
Adresse IP du serveur suivant
0.0.0.0
Chaîne
4
Adresse IP du serveur (si connue)

23
Adresse IP de l'agent de relais
172.16.114.41
Chaîne
4
Adresse IP de l'agent de relais (par exemple, un switch)

27
Adresse MAC du client
14:d6:4d:a7:c9:55
Hex
6
Adresse MAC de l'expéditeur du paquet (client)

31
Remplissage de l'adresse matérielle du client
 
Hex
10
Espace réservé. Généralement rempli de zéros

41
Nom d'hôte du serveur
 
Chaîne
64
Nom du serveur DHCP. Souvent non transmis

105
Nom du fichier d'amorçage
 
Chaîne
128
Nom de fichier sur le serveur utilisé par les stations sans disque lors du démarrage

235
Cookie magique
63:82:53:63
Hex
4
Le « nombre magique » permettant notamment de déterminer que ce paquet appartient au protocole DHCP

Options DHCP. Elles peuvent apparaître dans n'importe quel ordre

236
Numéro de l'option
53
Déc
3
Option 53, définissant le type de paquet DHCP 3 — DHCPREQUEST

 
Longueur de l'option
1
Déc
1

 
Valeur de l'option
3
Déc
1

 
Numéro de l'option
61
Déc
1
Identifiant du client : 01 (pour Ethernet) + adresse MAC du client

 
Longueur de l'option
7
Déc
1

 
Valeur de l'option
01:2c:ab:25:ff:72:a6
Hex
7

 
Numéro de l'option
60
Déc
 
"Identifiant de classe de fournisseur". Dans mon cas, il indique la version du client DHCP. Il est possible que d'autres appareils retournent autre chose. Windows, par exemple, indique MSFT 5.0.

 
Longueur de l'option
11
Déc
 

 
Valeur de l'option
udhcp 0.9.8
Chaîne
 

 
Numéro de l'option
55
 
1
Paramètres réseau demandés par le client. Leur composition peut varier.

01 — Masque de réseau
03 — Passerelle
06 — DNS
oc — Nom d'hôte
0f — Nom de domaine réseau
1c — Adresse de diffusion (broadcast)
42 — Nom du serveur TFTP
79 — Route statique sans classe

 
Longueur de l'option
8
 
1

 
Valeur de l'option
01:03:06:0c:0f:1c:42:79
 
8

 
Numéro de l'option
82
Déc
1
Option 82, qui répète ce qui est venu dans le DHCPDISCOVER

 
Longueur de l'option
18
Déc
1

 
Valeur de l'option
01:08:00:06:00
01:01:00:00:01
02:06:00:03:0f
26:4d:ec
Déc
18

 
Fin du paquet
255
Déc
1
255 symbolise la fin du paquet

DHCPACK

En tant que confirmation que "oui, c'est bien ton adresse IP, et je ne la donnerai à personne d'autre" de la part du serveur DHCP, le paquet au format DHCPACK est envoyé du serveur au client. Tout comme les autres paquets, il est également envoyé par diffusion. Cependant, dans le code ci-dessous pour le serveur DHCP réalisé en Python, je duplique, par précaution, toute demande de diffusion en envoyant un paquet à l'adresse IP spécifique du client, si elle est déjà connue. De plus, le serveur DHCP ne se soucie absolument pas de savoir si le paquet DHCPACK a atteint le client. Si le client ne reçoit pas le DHCPACK, il répète simplement le DHCPREQUEST après un certain temps.

Table de structure du paquet DHCPACK

Position dans le paquet
Nom de la valeur (usage courant)
Exemple
Représentation
Octet
Explication

1
Boot Request
2
Hex
1
Type de message. 1 — demande du client au serveur, 2 — réponse du serveur au client

2
Type de matériel
1
Hex
1
Type d'adresse matérielle, dans ce protocole 1 — MAC

3
Longueur de l'adresse matérielle
6
Hex
1
Longueur de l'adresse MAC de l'appareil

4
Sauts
1
Hex
1
Nombre de routes intermédiaires

5
Identifiant de transaction
23:cf:de:1d
Hex
4
Identifiant unique de la transaction. Généré par le client au début de l'opération de demande

7
Secondes écoulées
0
Hex
4
Temps en secondes depuis le début du processus d'obtention de l'adresse

9
Bootp flags
8000
Hex
2
Certains drapeaux qui peuvent être définis comme indication des paramètres du protocole. Dans ce cas, le "broadcast" est activé.

11
Adresse IP du client
0.0.0.0
Chaîne
4
Adresse IP du client (si elle existe)

15
Votre adresse IP client
172.16.134.61
Chaîne
4
Adresse IP proposée par le serveur (si elle existe)

19
Adresse IP du serveur suivant
0.0.0.0
Chaîne
4
Adresse IP du serveur (si connue)

23
Adresse IP de l'agent de relais
172.16.114.41
Chaîne
4
Adresse IP de l'agent de relais (par exemple, un switch)

27
Adresse MAC du client
14:d6:4d:a7:c9:55
Hex
6
Adresse MAC de l'expéditeur du paquet (client)

31
Remplissage de l'adresse matérielle du client
 
Hex
10
Espace réservé. Généralement rempli de zéros

41
Nom d'hôte du serveur
 
Chaîne
64
Nom du serveur DHCP. Souvent non transmis

105
Nom du fichier d'amorçage
 
Chaîne
128
Nom de fichier sur le serveur utilisé par les stations sans disque lors du démarrage

235
Cookie magique
63:82:53:63
Hex
4
Le « nombre magique » permettant notamment de déterminer que ce paquet appartient au protocole DHCP

Options DHCP. Elles peuvent apparaître dans n'importe quel ordre

236
Numéro de l'option
53
Déc
3
Option 53, définissant le type de paquet DHCP 5 — DHCPACK

 
Longueur de l'option
1
Déc
1

 
Valeur de l'option
5
Déc
1

 
Numéro de l'option
1
Déc
1
Option proposant au client DHCP un masque de réseau

 
Longueur de l'option
4
Déc
1

 
Valeur de l'option
255.255.224.0
Chaîne
4

 
Numéro de l'option
3
Déc
1
Option proposant au client DHCP une passerelle par défaut

 
Longueur de l'option
4
Déc
1

 
Valeur de l'option
172.16.12.1
Chaîne
4

 
Numéro de l'option
6
Déc
1
Option proposant au client DHCP un serveur DNS

 
Longueur de l'option
4
Déc
1

 
Valeur de l'option
8.8.8.8
Chaîne
4

 
Numéro de l'option
51
Déc
1
Durée de vie des paramètres réseau accordés en secondes, après quoi le client DHCP doit les demander à nouveau

 
Longueur de l'option
4
Déc
1

 
Valeur de l'option
86400
Déc
4

 
Numéro de l'option
82
Déc
1
Option 82, qui répète ce qui est venu dans le DHCPDISCOVER

 
Longueur de l'option
18
Déc
1

 
Valeur de l'option
01:08:00:06:00
01:01:00:00:01
02:06:00:03:0f
26:4d:ec
Déc
18

 
Fin du paquet
255
Déc
1
255 symbolise la fin du paquet

Installation

L'installation consiste en l'installation des modules Python nécessaires au bon fonctionnement. Il est supposé que MySQL est déjà installé et configuré.

FreeBSD

pkg install python3
python3 -m ensurepip
pip3 install mysql-connector

Ubuntu

sudo apt-get install python3
sudo apt-get install pip3
sudo pip3 install mysql-connector

Créons une base de données MySQL, chargeons-y le dump pydhcp.sql et configurons le fichier de configuration.

Configuration

Tous les paramètres du serveur sont dans un fichier au format xml. Fichier de référence :

0.0.0.0
        255.255.255.255
        192.168.0.71
	8600
	1
        255.255.255.0
        192.168.0.1
        8.8.8.8
    
    
        localhost
	test
	test
	pydhcp
    
    
       option_82_hex:sw_port1:20:22       
       option_82_hex:sw_port2:16:18       
       option_82_hex:sw_mac:26:40
    
    
        3
	select ip,mask,router,dns from users where upper(mac)=upper('{option_82_AgentRemoteId_hex}') and upper(port)=upper('{option_82_AgentCircuitId_port_hex}')
        select ip,mask,router,dns from users where upper(mac)=upper('{sw_mac}') and upper(port)=upper('{sw_port2}')
        select ip,mask,router,dns from users where upper(mac)=upper('{ClientMacAddress}')
	insert into history (id,dt,mac,ip,comment) values (null,now(),'{ClientMacAddress}','{RequestedIpAddress}','DHCPACK/INFORM')

Examinons maintenant les balises :

La section dhcpserver décrit les paramètres principaux pour le démarrage du serveur, à savoir :

  • host — quelle adresse IP le serveur écoute sur le port 67
  • broadcast — quelle IP est le broadcast pour DHCPOFFER et DHCPACK
  • DHCPServer — quelle IP a le serveur DHCP
  • LeaseTime — durée de location de l'adresse IP attribuée
  • ThreadLimit — combien de threads peuvent être lancés simultanément pour traiter les paquets UDP arrivant sur le port 67. Cela est supposé aider sur des projets à forte charge 😉
  • defaultMask, defaultRouter, defaultDNS — ce qui est proposé par défaut au Client si l'IP est trouvée dans la base, mais sans paramètres supplémentaires spécifiés

Section mysql :

host, username, password, basename — tout cela est explicite. Une structure approximative de la base de données est publiée sur GitHub

Section query : ici, les requêtes pour obtenir OFFER/ACK sont décrites :

  • offer_count — le nombre de lignes avec des requêtes qui retournent un résultat sous la forme ip, mask, router, dns
  • offer_n — ligne de requête. Si la réponse est vide, elle exécute la requête offer suivante
  • history_sql — requête qui écrit, par exemple, dans "l'historique d'autorisation" du Client

Dans les requêtes, toutes les variables de la section options ou les options du protocole DHCP peuvent être utilisées.

Section options. C'est ici que cela devient plus intéressant. Nous pouvons créer des variables que nous pourrons utiliser plus tard dans la section query.

Par exemple :

option_82_hex:sw_port1:20:22

, cette ligne de commande prend toute la chaîne reçue dans la requête DHCP option 82, au format hex, dans la plage de 20 à 22 octets inclusivement et la place dans une nouvelle variable sw_port1 (port du commutateur d'où la requête est venue)

option_82_hex:sw_mac:26:40

, nous définissons la variable sw_mac en prenant l'hex dans la plage 26:40

Pour voir toutes les options possibles pouvant être utilisées dans les requêtes, on peut démarrer le serveur avec la clé -d. Nous verrons un journal similaire :

-- un paquet DHCPINFORM est arrivé sur le port 67, de 0025224ad764, b'x91xa5xe0xa3xa5xa9-x8fx8a', ('172.30.114.25', 68)
{'ClientMacAddress': '0025224ad764',
 'ClientMacAddressByte': b'x00%"Jxd7d',
 'HType': 'Ethernet',
 'HostName': b'x91xa5xe0xa3xa5xa9-x8fx8a',
 'ReqListDNS': True,
 'ReqListDomainName': True,
 'ReqListPerfowmRouterDiscover': True,
 'ReqListRouter': True,
 'ReqListStaticRoute': True,
 'ReqListSubnetMask': True,
 'ReqListVendorSpecInfo': 43,
 'RequestedIpAddress': '0.0.0.0',
 'Vendor': b'MSFT 5.0',
 'chaddr': '0025224ad764',
 'ciaddr': '172.30.128.13',
 'flags': b'x00x00',
 'giaddr': '172.30.114.25',
 'gpoz': 308,
 'hlen': 6,
 'hops': 1,
 'htype': 'MAC',
 'magic_cookie': b'cx82Sc',
 'op': 'DHCPINFORM',
 'option12': 12,
 'option53': 53,
 'option55': 55,
 'option60': 60,
 'option61': 61,
 'option82': 82,
 'option_82_byte': b'x12x01x06x00x04x00x01x00x06x02x08x00'
                   b'x06x00x1eXx9exb2xad',
 'option_82_hex': '12010600040001000602080006001e589eb2ad',
 'option_82_len': 18,
 'option_82_str': "b'x12x01x06x00x04x00x01x00x06x02x08x00x06x00x1eXx9exb2xad'",
 'result': False,
 'secs': 768,
 'siaddr': '0.0.0.0',
 'sw_mac': '001e589eb2ad',
 'sw_port1': '06',
 'xidbyte': b'<x89}x8c',
 'xidhex': '3c897d8c',
 'yiaddr': '0.0.0.0'}

Nous pouvons donc envelopper n'importe quelle variable dans {} et elle sera utilisée dans la requête SQL.

Notons pour l'historique que l'adresse IP que le client a reçu :

Serveur DHCP+Mysql en Python

Serveur DHCP+Mysql en Python

Démarrage du serveur

.\/pydhcpdb.py -d -c config.xml

— d mode affichage dans la console DEBUG
— c <nom_fichier> fichier de configuration

Analyse des résultats

Et maintenant, plus de détails sur la mise en œuvre du serveur en Python. C'est compliqué. Python a été étudié « en cours de route ». De nombreux aspects ont été réalisés dans un style : « waouh, j'ai fait quelque chose qui fonctionne ». Pas du tout optimisés et laissés tels quels principalement en raison d'un manque d'expérience en développement sur python. Je vais m'attarder sur les aspects les plus intéressants de la mise en œuvre du serveur dans le « code ».

Analyseur de fichier de configuration XML

Utilise le module standard Python xml.dom. Cela semble simple, mais lors de la mise en œuvre, il manquait énormément de documentation utile et d'exemples en ligne avec l'utilisation de ce module.

    tree = minidom.parse(gconfig["config_file"])
    mconfig=tree.getElementsByTagName("mysql")
    for elem in mconfig:        
        gconfig["mysql_host"]=elem.getElementsByTagName("host")[0].firstChild.data      
        gconfig["mysql_username"]=elem.getElementsByTagName("username")[0].firstChild.data      
        gconfig["mysql_password"]=elem.getElementsByTagName("password")[0].firstChild.data      
        gconfig["mysql_basename"]=elem.getElementsByTagName("basename")[0].firstChild.data      
    dconfig=tree.getElementsByTagName("dhcpserver")
    for elem in dconfig:        
        gconfig["broadcast"]=elem.getElementsByTagName("broadcast")[0].firstChild.data      
        gconfig["dhcp_host"]=elem.getElementsByTagName("host")[0].firstChild.data      
        gconfig["dhcp_LeaseTime"]=elem.getElementsByTagName("LeaseTime")[0].firstChild.data      
        gconfig["dhcp_ThreadLimit"]=int(elem.getElementsByTagName("ThreadLimit")[0].firstChild.data)              
        gconfig["dhcp_Server"]=elem.getElementsByTagName("DHCPServer")[0].firstChild.data              
        gconfig["dhcp_defaultMask"]=elem.getElementsByTagName("defaultMask")[0].firstChild.data              
        gconfig["dhcp_defaultRouter"]=elem.getElementsByTagName("defaultRouter")[0].firstChild.data              
        gconfig["dhcp_defaultDNS"]=elem.getElementsByTagName("defaultDNS")[0].firstChild.data              
    qconfig=tree.getElementsByTagName("query")
    for elem in qconfig:  
        gconfig["offer_count"]=elem.getElementsByTagName("offer_count")[0].firstChild.data                          
        for num in range(int(gconfig["offer_count"])):
            gconfig["offer_"+str(num+1)]=elem.getElementsByTagName("offer_"+str(num+1))[0].firstChild.data      
        gconfig["history_sql"]=elem.getElementsByTagName("history_sql")[0].firstChild.data                          
    options=tree.getElementsByTagName("options")       
    for elem in options:          
        node=elem.getElementsByTagName("option")
        for options in node:
            optionsMod.append(options.firstChild.data)

Multithreading

Étonnamment, la multithreading en Python est implémentée de manière très claire et simple.

def PacketWork(data, addr): 
...
# implémentation de l'analyse du paquet reçu et de la réponse à celui-ci
...


while True:
    data, addr = udp_socket.recvfrom(1024) # en attente d'un paquet UDP
    thread = threading.Thread(target=PacketWork, args=(data, addr,)).start()	# dès qu'il arrive, on lance en arrière-plan la fonction PacketWork précédemment définie avec les paramètres
    while threading.active_count() > gconfig["dhcp_ThreadLimit"]:
       time.sleep(1) # si le nombre de threads lancés dépasse les réglages, on attend qu'il diminue

Réception/envoi de paquets DHCP

Pour intercepter les paquets UDP passant par la carte réseau, il faut "lever" le socket :

udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
udp_socket.bind((gconfig["dhcp_host"], 67))

, où les indicateurs :

  • AF_INET — signifie que le format de l'adresse sera IP : port. Il peut également y avoir AF_UNIX — où l'adresse est spécifiée par le nom du fichier.
  • SOCK_DGRAM signifie que nous recevons non pas un "paquet brut", mais déjà traité par un pare-feu, et avec un paquet partiellement tronqué. Autrement dit, nous obtenons uniquement le paquet UDP sans l'enveloppe "physique" du paquet UDP. Si l'on utilise le drapeau SOCK_RAW, il faudra également analyser cette "enveloppe".

L'envoi d'un paquet peut se faire soit en mode diffusion :

                    udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) # Met le socket en mode envoi de diffusion
                    rz=udp_socket.sendto(packetack, (gconfig["broadcast"],68))

, soit à l'adresse d'où le paquet est arrivé :

                        udp_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # Met le socket en mode "plusieurs auditeurs"
                        rz=udp_socket.sendto(packetack, addr)

, où SOL_SOCKET désigne le "niveau de protocole" pour définir des options,

, SO_BROADCAST est l'option qui indique que le paquet est envoyé en mode "diffusion",

  , SO_REUSEADDR est l'option qui passe le socket en mode "plusieurs auditeurs". En théorie, elle n'est pas nécessaire dans ce cas, mais sur l'un des serveurs FreeBSD où j'ai testé, le code ne fonctionnait pas sans cette option.

Analyse du paquet DHCP

C'est ici que Python m'a vraiment plu. Il semble qu'il permette de manipuler le bytecode de manière assez libre dès la sortie de la boîte. Ce qui facilite grandement sa conversion en valeurs décimales, chaînes et hexadécimales — en d'autres termes, ce dont nous avons réellement besoin pour comprendre la structure du paquet. Par exemple, il est possible d'obtenir une plage d'octets en HEX et en octets simples :

    res["xidhex"]=data[4:8].hex()
    res["xidbyte"]=data[4:8]

, d'emballer des octets dans une structure :

res["flags"]=pack('BB',data[10],data[11])

Obtenir l'IP à partir de la structure :

res["ciaddr"]=socket.inet_ntoa(pack('BBBB',data[12],data[13],data[14],data[15]));

Et vice versa :

res=res+socket.inet_pton(socket.AF_INET, gconfig["dhcp_Server"])

C'est tout 😉

Source : habr.com

Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS 🔥 Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster