
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 (mon fork), l'original , 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 .
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 :
- 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.
- 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.
- 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.
- 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.

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 :
![]()
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
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 :


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 diminueRé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
