
Le but de ce projet était:
- Apprendre DHCP sur un réseau IPv4
- Apprendre Python (un peu plus qu'à partir de zéro;))
- remplaçant le serveur DB2DHCP (ma fourchette), l'original ici , qui est de plus en plus difficile à assembler sous le nouveau système d'exploitation. Oui, et je n'aime pas ça le binaire, qui n'est pas possible de "changer maintenant"
- obtenir un serveur DHCP fonctionnel avec la possibilité de sélectionner l'adresse IP de l'abonné par le mac de l'abonné ou un groupe de commutateurs mac + port (Option 82)
- écrire un autre vélo (Oh! c'est mon passe-temps préféré)
- obtenir Lyuli de son strabisme sur Habrahabr (ou mieux inviter);)
Résultat: ça marche;) Testé sur FreeBSD et Ubuntu OS. Théoriquement, le code peut être demandé pour fonctionner sous n'importe quel système d'exploitation, car il n'y a pas de liaisons spécifiques dans le code.
Attention Beaucoup plus loin.
Lien vers le référentiel pour que les fans puissent
«toucher vivant» .
Le processus d'installation, de configuration et d'utilisation du résultat de "l'étude du matériel" est beaucoup plus faible, puis un peu de théorie sur le protocole DHCP. Pour moi. Et pour l'histoire;)
Un peu de théorie
Qu'est-ce que DHCP?
Il s'agit d'un protocole réseau qui permet à un appareil de connaître son adresse IP (enfin, d'autres paramètres comme une passerelle, DNS, etc.) à partir d'un serveur DHCP. Les paquets sont échangés via UDP. Le principe général de fonctionnement de l'appareil lors de la demande de paramètres réseau est le suivant:
- Le périphérique (client) envoie une demande de diffusion UDP (DHCPDISCOVER) sur le réseau avec la demande: «Eh bien, quelqu'un, donnez-moi une adresse IP». Et généralement (mais pas toujours), la demande provient du port 68 (source) et la destination est le port 67 (destination). Certains appareils envoient également des paquets à partir du port 67. À l'intérieur du paquet DHCPDISCOVER, l'adresse MAC du périphérique client est incluse.
- Tous les serveurs DHCP situés sur le réseau (et il peut y en avoir plusieurs) forment le périphérique qui a envoyé à DHCPDISCOVER une proposition DHCPOFFER avec les paramètres réseau, et il la diffuse également sur le réseau. L'identification à laquelle ce paquet est destiné va à l'adresse MAC du client fournie précédemment dans la requête DHCPDISCOVER.
- Le client reçoit des paquets avec des offres de paramètres réseau, sélectionne les plus attractifs (les critères peuvent être différents, par exemple, au moment de la livraison des paquets, le nombre de routes intermédiaires), et fait que le serveur DHCP "demande officiellement" DHCPREQUEST avec des paramètres réseau. Dans ce cas, le paquet va à un serveur DHCP spécifique.
- Le serveur qui a reçu DHCPREQUEST envoie un paquet DHCPACK dans lequel il répertorie à nouveau les paramètres réseau pour ce client

En outre, il existe des paquets DHCPINFORM qui proviennent du client et dont le but est d'informer le serveur DHCP que le «client est vivant» et utilise les paramètres réseau émis. Dans l'implémentation de ce serveur, ces paquets sont ignorés.
Format de paquet
En général, la trame de paquet Ethernet ressemble à ceci:

Dans notre cas, nous ne considérerons que les données directement du contenu du paquet UDP, sans les en-têtes de protocole de couche OSI, à savoir la structure DHCP:
DHCPDISCOVER
Ainsi, le processus d'obtention de l'adresse IP pour le périphérique commence par le fait que le client DHCP envoie une demande de diffusion du port 68 à 255.255.255.255:67. Dans ce package, le client inclut son adresse MAC, ainsi que ce qu'il souhaite recevoir du serveur DHCP. La structure du package est décrite dans le tableau ci-dessous.
Table de structure de paquets DHCPDISCOVERPosition du paquet | Nom de la valeur | Exemple | Soumission | Octet | Explication |
1 | Demande de démarrage | 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 | Le matériel adhère à la longueur | 6 | Hex | 1 | Longueur de l'adresse MAC de l'appareil |
4 | Le houblon | 1 | Hex | 1 | Nombre de routes intermédiaires |
5 | Identifiant de transaction | 23: cf: de: 1d | Hex | 4 | Identifiant de transaction unique. Généré par le client au début de l'opération de demande |
7 | Deuxième écoulé | 0 | Hex | 4 | Temps en secondes depuis le début du processus d'obtention de l'adresse |
9 | Drapeaux Bootp | 0 | Hex | 2 | Quelques drapeaux qui peuvent être définis comme une indication des paramètres du protocole |
11 | Adresse IP du client | 0.0.0.0 | String | 4 | Adresse IP du client (le cas échéant) |
15 | L'adresse IP de votre client | 0.0.0.0 | String | 4 | Adresse IP proposée par le serveur (le cas échéant) |
19 | Adresse IP du serveur suivant | 0.0.0.0 | String | 4 | Adresse IP du serveur (si connue) |
23 | Adresse IP de l'agent relais | 172.16.114.41 | String | 4 | Adresse IP de l'agent relais (par exemple, un commutateur) |
27 | Adresse MAC du client | 14: d6: 4d: a7: c9: 55 | Hex | 6 | Adresse MAC de l'expéditeur du paquet (client) |
31 | Remplissage d'adresse du matériel client | | Hex | 10 | Place réservée. Généralement des zéros |
41 | Nom d'hôte du serveur | | String | 64 | Le nom du serveur DHCP. Habituellement non transmis |
105 | Nom du fichier de démarrage | | String | 128 | Le 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", par lequel incl. vous pouvez déterminer que ce paquet appartient au protocole DHCP |
Options DHCP. Peut aller dans n'importe quel ordre |
236 | Numéro d'option | 53 | Déc | 1 | Option 53 spécifiant le type de paquet DHCP
1 - DHCPDISCOVER 3 - DEMANDE DHCP 2 - DHCPOFFER 5 - DHCPACK 8 - DHCPINFORM |
| Longueur d'option | 1 | Déc | 1 |
| Valeur d'option | 1 | Déc | 1 |
| Numéro d'option | 50 | Déc | 1 | Quelle adresse IP le client souhaite-t-il recevoir |
| Longueur d'option | 4 | Déc | 1 |
| Valeur d'option | 172.16.134.61 | String | 4 |
| Numéro d'option | 55 | | 1 | Les paramètres réseau demandés par le client. La composition peut être différente
01 - Masque de réseau 03 - Passerelle 06 - DNS oc - nom d'hôte 0f - nom de domaine du réseau 1c - adresse de la demande de diffusion (diffusion) 42 - Nom du serveur TFTP 79 - Route statique sans classe |
| Longueur d'option | 8 | | 1 |
| Valeur d'option | 01: 03: 06: 0c: 0f: 1c: 42: 79 | | 8 |
| Numéro d'option | 82 | Déc | | Option 82, dans laquelle l'adresse MAC du répéteur et certaines valeurs supplémentaires sont transmises.
Le plus souvent, le port du commutateur sur lequel s'exécute le client DHCP final. Des options supplémentaires sont «intégrées» dans cette option. Le premier octet est le numéro de la «sous-option», le second est sa longueur, puis sa valeur.
Dans ce cas, dans l'option 82, les sous-options sont imbriquées: ID de circuit d'agent = 00: 04: 00: 01: 00: 04, où les deux derniers octets sont le port client DHCP d'où provient la demande
ID distant de l'agent = 00: 06: c8: be: 19: 93: 11: 48 - Adresse MAC du périphérique relais DHCP |
| Longueur d'option | 18 | Déc | |
| Valeur d'option | 01:06 00: 04: 00: 01: 00: 04 02:08 00: 06: c8: soit: 19: 93: 11: 48 | Hex | |
| Fin du colis | 255 | Déc | 1 | 255 symbolise la fin du colis |
DHCPOFFER
Dès que le serveur reçoit le paquet DHCPDISCOVER et s'il voit qu'il peut offrir quelque chose au client à partir de la demande, il forme alors une réponse - DHCPOFFER. La réponse est envoyée au port "d'où elle vient", Broadcast, car à ce moment, le client n'a pas encore d'adresse IP, il ne peut donc recevoir un paquet que s'il est envoyé en diffusion. Le client reconnaît qu'il s'agit d'un paquet pour lui par MAC à son adresse à l'intérieur du paquet, ainsi que le numéro de transaction qu'il génère au moment de la création du premier paquet.
Table de structure de paquets DHCPOFFERPosition du paquet | Nom de la valeur (commun) | Exemple | Soumission | Octet | Explication |
1 | Demande de démarrage | 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 | Le matériel adhère à la longueur | 6 | Hex | 1 | Longueur de l'adresse MAC de l'appareil |
4 | Le houblon | 1 | Hex | 1 | Nombre de routes intermédiaires |
5 | Identifiant de transaction | 23: cf: de: 1d | Hex | 4 | Identifiant de transaction unique. Généré par le client au début de l'opération de demande |
7 | Deuxième écoulé | 0 | Hex | 4 | Temps en secondes depuis le début du processus d'obtention de l'adresse |
9 | Drapeaux Bootp | 0 | Hex | 2 | Quelques drapeaux qui peuvent être définis comme une indication des paramètres du protocole. Dans ce cas, 0 signifie le type de demande de monodiffusion |
11 | Adresse IP du client | 0.0.0.0 | String | 4 | Adresse IP du client (le cas échéant) |
15 | L'adresse IP de votre client | 172.16.134.61 | String | 4 | Adresse IP proposée par le serveur (le cas échéant) |
19 | Adresse IP du serveur suivant | 0.0.0.0 | String | 4 | Adresse IP du serveur (si connue) |
23 | Adresse IP de l'agent relais | 172.16.114.41 | String | 4 | Adresse IP de l'agent relais (par exemple, un commutateur) |
27 | Adresse MAC du client | 14: d6: 4d: a7: c9: 55 | Hex | 6 | Adresse MAC de l'expéditeur du paquet (client) |
31 | Remplissage d'adresse du matériel client | | Hex | 10 | Place réservée. Généralement des zéros |
41 | Nom d'hôte du serveur | | String | 64 | Le nom du serveur DHCP. Habituellement non transmis |
105 | Nom du fichier de démarrage | | String | 128 | Le 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", par lequel incl. vous pouvez déterminer que ce paquet appartient au protocole DHCP |
Options DHCP. Peut aller dans n'importe quel ordre |
236 | Numéro d'option | 53 | Déc | 1 | Option 53 spécifiant le type de paquet DHCP 2 - DHCPOFFER |
| Longueur d'option | 1 | Déc | 1 |
| Valeur d'option | 2 | Déc | 1 |
| Numéro d'option | 1 | Déc | 1 | Option offrant un masque de réseau client DHCP |
| Longueur d'option | 4 | Déc | 1 |
| Valeur d'option | 255.255.224.0 | String | 4 |
| Numéro d'option | 3 | Déc | 1 | Option offrant la passerelle par défaut du client DHCP |
| Longueur d'option | 4 | Déc | 1 |
| Valeur d'option | 172.16.12.1 | String | 4 |
| Numéro d'option | 6 | Déc | 1 | Option offrant DHCP au client DNS |
| Longueur d'option | 4 | Déc | 1 |
| Valeur d'option | 8.8.8.8 | String | 4 |
| Numéro d'option | 51 | Déc | 1 | La durée de vie des paramètres réseau émis en secondes, après quoi le client DHCP doit les demander à nouveau |
| Longueur d'option | 4 | Déc | 1 |
| Valeur d'option | 86400 | Déc | 4 |
| Numéro d'option | 82 | Déc | 1 | L'option 82, répète ce qui est arrivé dans DHCPDISCOVER |
| Longueur d'option | 18 | Déc | 1 |
| Valeur d'option | 01: 08: 00: 06: 00 01: 01: 00: 00: 01 02: 06: 00: 03: 0f 26: 4d: ec | Déc | 18 |
| Fin du colis | 255 | Déc | 1 | 255 symbolise la fin du colis |
DHCPREQUEST
Une fois que le client a reçu DHCPOFFER, il forme un paquet avec une demande de paramètres réseau non pas à tous les serveurs DHCP du réseau, mais uniquement à un serveur spécifique, dont la proposition DHCPOFFER lui a le plus «plu». Les critères de «similaires» peuvent être différents et dépendent de la mise en œuvre du client DHCP. Le destinataire de la demande est indiqué à l'aide de l'adresse MAC du serveur DHCP. En outre, le paquet DHCPREQUEST peut être envoyé par le client sans former DHCPDISCOVER plus tôt, si l'adresse IP du serveur a été précédemment reçue précédemment.
Table de structure de paquets DHCPREQUESTPosition du paquet | Nom de la valeur (commun) | Exemple | Soumission | Octet | Explication |
1 | Demande de démarrage | 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 | Le matériel adhère à la longueur | 6 | Hex | 1 | Longueur de l'adresse MAC de l'appareil |
4 | Le houblon | 1 | Hex | 1 | Nombre de routes intermédiaires |
5 | Identifiant de transaction | 23: cf: de: 1d | Hex | 4 | Identifiant de transaction unique. Généré par le client au début de l'opération de demande |
7 | Deuxième écoulé | 0 | Hex | 4 | Temps en secondes depuis le début du processus d'obtention de l'adresse |
9 | Drapeaux Bootp | 8000 | Hex | 2 | Quelques drapeaux qui peuvent être définis comme une indication des paramètres du protocole. Dans ce cas, "Diffusion" |
11 | Adresse IP du client | 0.0.0.0 | String | 4 | Adresse IP du client (le cas échéant) |
15 | L'adresse IP de votre client | 172.16.134.61 | String | 4 | Adresse IP proposée par le serveur (le cas échéant) |
19 | Adresse IP du serveur suivant | 0.0.0.0 | String | 4 | Adresse IP du serveur (si connue) |
23 | Adresse IP de l'agent relais | 172.16.114.41 | String | 4 | Adresse IP de l'agent relais (par exemple, un commutateur) |
27 | Adresse MAC du client | 14: d6: 4d: a7: c9: 55 | Hex | 6 | Adresse MAC de l'expéditeur du paquet (client) |
31 | Remplissage d'adresse du matériel client | | Hex | 10 | Place réservée. Généralement des zéros |
41 | Nom d'hôte du serveur | | String | 64 | Le nom du serveur DHCP. Habituellement non transmis |
105 | Nom du fichier de démarrage | | String | 128 | Le 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", par lequel incl. vous pouvez déterminer que ce paquet appartient au protocole DHCP |
Options DHCP. Peut aller dans n'importe quel ordre |
236 | Numéro d'option | 53 | Déc | 3 | Option 53 spécifiant le type de paquet DHCP 3 - DHCPREQUEST |
| Longueur d'option | 1 | Déc | 1 |
| Valeur d'option | 3 | Déc | 1 |
| Numéro d'option | 61 | Déc | 1 | ID client: 01 (pour Ethernet) + adresse MAC du client |
| Longueur d'option | 7 | Déc | 1 |
| Valeur d'option | 01: 2c: ab: 25: ff: 72: a6 | Hex | 7 |
| Numéro d'option | 60 | Déc | | "Identifiant de classe de fournisseur." Dans mon cas, la version du client DHCP est signalée. Peut-être que d'autres appareils retournent autre chose. Windows par exemple rapporte MSFT 5.0 |
| Longueur d'option | 11 | Déc | |
| Valeur d'option | udhcp 0.9.8 | String | |
| Numéro d'option | 55 | | 1 | Les paramètres réseau demandés par le client. La composition peut être différente
01 - Masque de réseau 03 - Passerelle 06 - DNS oc - nom d'hôte 0f - nom de domaine du réseau 1c - adresse de la demande de diffusion (diffusion) 42 - Nom du serveur TFTP 79 - Route statique sans classe |
| Longueur d'option | 8 | | 1 |
| Valeur d'option | 01: 03: 06: 0c: 0f: 1c: 42: 79 | | 8 |
| Numéro d'option | 82 | Déc | 1 | L'option 82, répète ce qui est arrivé dans DHCPDISCOVER |
| Longueur d'option | 18 | Déc | 1 |
| Valeur d'option | 01: 08: 00: 06: 00 01: 01: 00: 00: 01 02: 06: 00: 03: 0f 26: 4d: ec | Déc | 18 |
| Fin du colis | 255 | Déc | 1 | 255 symbolise la fin du colis |
DHCPACK
Pour confirmer que «oui, c'est votre adresse IP, et je ne la donnerai à personne d'autre» du serveur DHCP, il y a un paquet DHCPACK du serveur au client. C'est le même que le reste des paquets envoyés en diffusion. Bien que, dans le code ci-dessous du serveur DHCP implémenté en Python, au cas où, je dupliquerais toute demande de diffusion en envoyant un paquet à une adresse IP client spécifique, si elle est déjà connue. De plus, le serveur DHCP ne se soucie pas du tout de savoir si le paquet DHCPACK a atteint le client. Si le client ne reçoit pas DHCPACK, après un certain temps, il répète simplement DHCPREQUEST
Tableau de structure de paquets DHCPACKPosition du paquet | Nom de la valeur (commun) | Exemple | Soumission | Octet | Explication |
1 | Demande de démarrage | 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 | Le matériel adhère à la longueur | 6 | Hex | 1 | Longueur de l'adresse MAC de l'appareil |
4 | Le houblon | 1 | Hex | 1 | Nombre de routes intermédiaires |
5 | Identifiant de transaction | 23: cf: de: 1d | Hex | 4 | Identifiant de transaction unique. Généré par le client au début de l'opération de demande |
7 | Deuxième écoulé | 0 | Hex | 4 | Temps en secondes depuis le début du processus d'obtention de l'adresse |
9 | Drapeaux Bootp | 8000 | Hex | 2 | Quelques drapeaux qui peuvent être définis comme une indication des paramètres du protocole. Dans ce cas, "Diffusion" |
11 | Adresse IP du client | 0.0.0.0 | String | 4 | Adresse IP du client (le cas échéant) |
15 | L'adresse IP de votre client | 172.16.134.61 | String | 4 | Adresse IP proposée par le serveur (le cas échéant) |
19 | Adresse IP du serveur suivant | 0.0.0.0 | String | 4 | Adresse IP du serveur (si connue) |
23 | Adresse IP de l'agent relais | 172.16.114.41 | String | 4 | Adresse IP de l'agent relais (par exemple, un commutateur) |
27 | Adresse MAC du client | 14: d6: 4d: a7: c9: 55 | Hex | 6 | Adresse MAC de l'expéditeur du paquet (client) |
31 | Remplissage d'adresse du matériel client | | Hex | 10 | Place réservée. Généralement des zéros |
41 | Nom d'hôte du serveur | | String | 64 | Le nom du serveur DHCP. Habituellement non transmis |
105 | Nom du fichier de démarrage | | String | 128 | Le 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", par lequel incl. vous pouvez déterminer que ce paquet appartient au protocole DHCP |
Options DHCP. Peut aller dans n'importe quel ordre |
236 | Numéro d'option | 53 | Déc | 3 | Option 53 spécifiant le type de paquet DHCP 5 - DHCPACK |
| Longueur d'option | 1 | Déc | 1 |
| Valeur d'option | 5 | Déc | 1 |
| Numéro d'option | 1 | Déc | 1 | Option offrant un masque de réseau client DHCP |
| Longueur d'option | 4 | Déc | 1 |
| Valeur d'option | 255.255.224.0 | String | 4 |
| Numéro d'option | 3 | Déc | 1 | Option offrant la passerelle par défaut du client DHCP |
| Longueur d'option | 4 | Déc | 1 |
| Valeur d'option | 172.16.12.1 | String | 4 |
| Numéro d'option | 6 | Déc | 1 | Option offrant DHCP au client DNS |
| Longueur d'option | 4 | Déc | 1 |
| Valeur d'option | 8.8.8.8 | String | 4 |
| Numéro d'option | 51 | Déc | 1 | La durée de vie des paramètres réseau émis en secondes, après quoi le client DHCP doit les demander à nouveau |
| Longueur d'option | 4 | Déc | 1 |
| Valeur d'option | 86400 | Déc | 4 |
| Numéro d'option | 82 | Déc | 1 | L'option 82, répète ce qui est arrivé dans DHCPDISCOVER |
| Longueur d'option | 18 | Déc | 1 |
| Valeur d'option | 01: 08: 00: 06: 00 01: 01: 00: 00: 01 02: 06: 00: 03: 0f 26: 4d: ec | Déc | 18 |
| Fin du colis | 255 | Déc | 1 | 255 symbolise la fin du colis |
L'installation
L'installation consiste en fait à installer les modules python nécessaires à la tâche. Il est supposé que MySQL est déjà installé et configuré.
Freebsd
pkg install python3
python3 -m assurepip
pip3 installer le connecteur mysql
Ubuntu
sudo apt-get install python3
sudo apt-get install pip3
sudo pip3 installe le connecteur mysql
Nous créons la base de données MySQL, remplissons le vidage pydhcp.sql, configurons le fichier de configuration.
La configuration
Tous les paramètres du serveur sont au format de fichier xml. Fichier de référence:
<? xml version = "1.0"?>
<config>
<dhcpserver>
<host> 0.0.0.0 </host>
<broadcast> 255.255.255.255 </broadcast>
<DHCPServer> 192.168.0.71 </DHCPServer>
<LeaseTime> 8600 </LeaseTime>
<ThreadLimit> 1 </ThreadLimit>
<defaultMask> 255.255.255.0 </defaultMask>
<defaultRouter> 192.168.0.1 </defaultRouter>
<defaultDNS> 8.8.8.8 </defaultDNS>
</dhcpserver>
<mysql>
<host> localhost </host>
<username> test </username>
<password> test </password>
<basename> pydhcp </basename>
</mysql>
<options>
<option> option_82_hex: sw_port1: 20:22 </option>
<option> option_82_hex: sw_port2: 16:18 </option>
<option> option_82_hex: sw_mac: 26: 40 </option>
</options>
<query>
<offer_count> 3 </offer_count>
<offer_1> sélectionnez ip, mask, router, dns parmi les utilisateurs où upper (mac) = upper ('{option_82_AgentRemoteId_hex}') et upper (port) = upper ('{option_82_AgentCircuitId_port_hex}') </offer_1>
<offer_2> sélectionnez ip, mask, router, dns parmi les utilisateurs où upper (mac) = upper ('{sw_mac}') et upper (port) = upper ('{sw_port2}') </offer_2>
<offer_3> sélectionnez ip, mask, router, dns parmi les utilisateurs où upper (mac) = upper ('{ClientMacAddress}') </offer_3>
<history_sql> insérer dans l'historique (id, dt, mac, ip, comment) des valeurs (null, now (), '{ClientMacAddress}', '{RequestedIpAddress}', 'DHCPACK / INFORM') </history_sql>
</query>
</config>
Maintenant plus en détail sur les balises:
La section dhcpserver décrit les paramètres de base pour démarrer le serveur, à savoir:
- hôte - quelle adresse IP le serveur écoute sur le port 67
- broadcast - quelle adresse IP est une diffusion pour DHCPOFFER et DHCPACK
- DHCPServer - quelle est l'ip du serveur DHCP
- Durée du bail LeaseTime de l'adresse IP émise
- ThreadLimit - combien de threads s'exécutent simultanément pour traiter les paquets UDP entrants sur le port 67. On suppose que cela aidera sur les projets très chargés;)
- defaultMask, defaultRouter, defaultDNS - ce qui est proposé par défaut à l'abonné si IP est trouvé dans la base de données, mais aucun paramètre supplémentaire n'est spécifié pour lui
Section Mysql:
hôte, nom d'utilisateur, mot de passe, nom de base - tout parle de lui-même. Exemple de structure de base de données publiée sur
GitHubSection Requête: Cette section décrit les demandes d'OFFRE / ACK:
- offer_count - le nombre de lignes avec des requêtes qui retournent un résultat de la forme ip, mask, router, dns
- offer_n est la chaîne de requête. Si le retour est vide, la demande d'offre suivante est exécutée
- history_sql - demande d'écriture, par exemple, à "l'historique des autorisations" par l'abonné
Toutes les variables de la section des options ou les options du protocole DHCP peuvent participer aux demandes.
Options de section. Ici, c'est déjà plus intéressant. Ici, nous pouvons créer des variables que nous pouvons utiliser plus tard dans la section de requête.
Par exemple:
option_82_hex:sw_port1:20:22
, cette ligne est la commande pour prendre la ligne entière qui est venue dans la demande DHCP de l'option 82, au format hexadécimal, dans la plage de 20 à 22 octets, inclusivement et la mettre dans la nouvelle variable sw_port1 (le port du commutateur d'où la demande est venue)
option_82_hex:sw_mac:26:40
, définissez la variable sw_mac, en prenant un hex de la plage 26:40
Vous pouvez voir toutes les options possibles qui peuvent être utilisées dans les requêtes en démarrant le serveur avec le commutateur -d. Nous verrons quelque chose comme ce journal:
- le paquet DHCPINFORM est arrivé sur le port 67, à partir de 0025224ad764, b '\ x91 \ xa5 \ xe0 \ xa3 \ xa5 \ xa9- \ x8f \ x8a', ('172.30.114.25', 68)
{'ClientMacAddress': '0025224ad764',
'ClientMacAddressByte': b '\ x00% "J \ xd7d',
'HType': 'Ethernet',
'HostName': b '\ x91 \ xa5 \ xe0 \ xa3 \ xa5 \ xa9- \ x8f \ x8a',
'ReqListDNS': Vrai,
'ReqListDomainName': Vrai,
'ReqListPerfowmRouterDiscover': Vrai,
'ReqListRouter': Vrai,
'ReqListStaticRoute': Vrai,
'ReqListSubnetMask': Vrai,
«ReqListVendorSpecInfo»: 43,
'RequestedIpAddress': '0.0.0.0',
«Vendeur»: b'MSFT 5.0 »,
«chaddr»: «0025224ad764»,
«ciaddr»: «172.30.128.13»,
'drapeaux': b '\ x00 \ x00',
«giaddr»: «172.30.114.25»,
«gpoz»: 308,
«hlen»: 6,
«houblon»: 1,
'htype': 'MAC',
'magic_cookie': b'c \ x82Sc ',
'op': 'DHCPINFORM',
«option12»: 12,
«option53»: 53,
«option55»: 55,
«option60»: 60,
«option61»: 61,
«option82»: 82,
'option_82_byte': b '\ x12 \ x01 \ x06 \ x00 \ x04 \ x00 \ x01 \ x00 \ x06 \ x02 \ x08 \ x00'
b '\ x06 \ x00 \ x1eX \ x9e \ xb2 \ xad',
'option_82_hex': '12010600040001000602080006001e589eb2ad',
'option_82_len': 18,
'option_82_str': "b '\\ x12 \\ x01 \\ x06 \\ x00 \\ x04 \\ x00 \\ x01 \\ x00 \\ x06 \\ x02 \\ x08 \\ x00 \\ x06 \\ x00 \ \ x1eX \\ x9e \\ xb2 \\ xad '",
«résultat»: Faux,
«secondes»: 768,
'siaddr': '0.0.0.0',
'sw_mac': '001e589eb2ad',
'sw_port1': '06',
'xidbyte': b '<\ x89} \ x8c',
«xidhex»: «3c897d8c»,
'yiaddr': '0.0.0.0'}
En conséquence, nous pouvons encapsuler n'importe quelle variable dans {} et elle sera utilisée dans la requête SQL.
Capturons pour l'historique que le client a reçu l'adresse IP:


Démarrage du serveur
./pydhcpdb.py -d -c config.xml
- mode de sortie d vers la console DEBUG
- c <nom_fichier> fichier de configuration
Débriefing
Et maintenant plus sur l'implémentation du serveur en Python. C'est une douleur. Le python a été étudié à la volée. De nombreux moments sont faits dans le style de: "wow, j'ai en quelque sorte fait ce qui fonctionne." Pas optimisé du tout, et laissé sous cette forme principalement en raison de la faible expérience de développement en python. Je m'attarderai sur les moments les plus intéressants de l'implémentation du serveur dans le "code".
Analyseur de fichier de configuration XML
Le module Python standard xml.dom est utilisé. Cela semble simple, mais au cours de la mise en œuvre, il y a eu un manque notable de documentation et d'exemples sensibles sur le réseau utilisant ce module.
tree = minidom.parse (gconfig ["config_file"])
mconfig = tree.getElementsByTagName ("mysql")
pour elem dans 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")
pour elem dans 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 ("requête")
pour elem dans qconfig:
gconfig ["offer_count"] = elem.getElementsByTagName ("offer_count") [0] .firstChild.data
pour num dans la plage (int (gconfig ["offer_count"])):
gconfig ["offre _" + str (num + 1)] = elem.getElementsByTagName ("offre _" + str (num + 1)) [0] .firstChild.data
gconfig ["history_sql"] = elem.getElementsByTagName ("history_sql") [0] .firstChild.data
options = tree.getElementsByTagName ("options")
pour elem dans les options:
node = elem.getElementsByTagName ("option")
pour les options dans le nœud:
optionsMod.append (options.firstChild.data)
Multithreading
Curieusement, le multithreading en Python est implémenté très clairement et simplement.
def PacketWork (données, addr):
...
# mise en œuvre de l'analyse du colis reçu, et la réponse à celle-ci
...
tandis que True:
data, addr = udp_socket.recvfrom (1024) # attendre le paquet UDP
thread = threading.Thread (target = PacketWork, args = (data, addr,)). start () # as it came - exécutez la fonction PacketWork définie précédemment en arrière-plan avec des paramètres
tandis que threading.active_count ()> gconfig ["dhcp_ThreadLimit"]:
time.sleep (1) # si le nombre de threads en cours d'exécution est supérieur à celui des paramètres, attendez qu'ils deviennent moins
Recevoir / envoyer un paquet DHCP
Afin d'intercepter les paquets UDP provenant de la carte réseau, vous devez "soulever" le socket:
udp_socket = socket.socket (socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
udp_socket.bind ((gconfig ["dhcp_host"], 67))
où les drapeaux sont:
- AF_INET - signifie que le format d'adresse sera IP: port. Peut-être AF_UNIX - où l'adresse est donnée par le nom de fichier.
- SOCK_DGRAM - signifie que nous n'acceptons pas un «paquet brut», mais déjà passé à travers un pare-feu, et avec un paquet partiellement coupé. C'est-à-dire nous obtenons uniquement le paquet UDP sans le composant «physique» du wrapper de paquets UDP. Si vous utilisez l'indicateur SOCK_RAW, vous devrez également analyser ce «wrapper» également.
L'envoi d'un paquet peut être comme une diffusion:
udp_socket.setsockopt (socket.SOL_SOCKET, socket.SO_BROADCAST, 1) # basculer la socket en mode Diffusion
rz = udp_socket.sendto (packetack, (gconfig ["broadcast"], 68))
, et à l'adresse "d'où vient le colis":
udp_socket.setsockopt (socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # basculer la socket en mode "plusieurs écouteurs"
rz = udp_socket.sendto (packetack, addr)
où SOL_SOCKET signifie "niveau de protocole" pour définir les options,
L'option SO_BROADCAST est que le package casque Broadcast
L'option SO_REUSEADDR fait basculer le socket en mode multi-écouteurs. En théorie, ce n'est pas nécessaire dans ce cas, mais sur l'un des serveurs FreeBSD sur lequel j'ai testé, le code ne fonctionnait pas sans cette option.
Analyse des paquets DHCP
C'est là que j'ai vraiment aimé Python. Il se trouve que la "boîte" vous permet de traiter assez facilement le bytecode. Permettant d'être très simple à traduire en valeurs décimales, chaînes et hexadécimal - c'est-à-dire ce dont nous avons réellement besoin pour comprendre la structure du package. Ainsi, par exemple, vous pouvez obtenir une plage d'octets en HEX et uniquement des octets:
res ["xidhex"] = data [4: 8] .hex ()
res ["xidbyte"] = données [4: 8]
, regroupez les octets dans une structure:
res ["flags"] = pack ('BB', data [10], data [11])
Obtenez l'IP de la structure:
res ["ciaddr"] = socket.inet_ntoa (pack ('BBBB', données [12], données [13], données [14], données [15]));
Et vice versa:
res = res + socket.inet_pton (socket.AF_INET, gconfig ["dhcp_Server"])
C'est tout;)