Serveur DHCP + Mysql en Python



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:

  1. 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.
  2. 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.
  3. 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.
  4. 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 DHCPDISCOVER
Position du paquetNom de la valeurExempleSoumissionOctetExplication
1Demande de démarrage1Hex1Type de message. 1 - demande du client au serveur, 2 - réponse du serveur au client
2Type de matériel1Hex1Type d'adresse matérielle, dans ce protocole 1 - MAC
3Le matériel adhère à la longueur6Hex1Longueur de l'adresse MAC de l'appareil
4Le houblon1Hex1Nombre de routes intermédiaires
5Identifiant de transaction23: cf: de: 1dHex4Identifiant de transaction unique. Généré par le client au début de l'opération de demande
7Deuxième écoulé0Hex4Temps en secondes depuis le début du processus d'obtention de l'adresse
9Drapeaux Bootp0Hex2Quelques drapeaux qui peuvent être définis comme une indication des paramètres du protocole
11Adresse IP du client0.0.0.0String4Adresse IP du client (le cas échéant)
15L'adresse IP de votre client0.0.0.0String4Adresse IP proposée par le serveur (le cas échéant)
19Adresse IP du serveur suivant0.0.0.0String4Adresse IP du serveur (si connue)
23Adresse IP de l'agent relais172.16.114.41String4Adresse IP de l'agent relais (par exemple, un commutateur)
27Adresse MAC du client14: d6: 4d: a7: c9: 55Hex6Adresse MAC de l'expéditeur du paquet (client)
31Remplissage d'adresse du matériel clientHex10Place réservée. Généralement des zéros
41Nom d'hôte du serveurString64Le nom du serveur DHCP. Habituellement non transmis
105Nom du fichier de démarrageString128Le nom de fichier sur le serveur utilisé par les stations sans disque lors du démarrage
235Cookie magique63: 82: 53: 63Hex4Le 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
236Numéro d'option53Déc1Option 53 spécifiant le type de paquet DHCP

1 - DHCPDISCOVER
3 - DEMANDE DHCP
2 - DHCPOFFER
5 - DHCPACK
8 - DHCPINFORM
Longueur d'option1Déc1
Valeur d'option1Déc1
Numéro d'option50Déc1Quelle adresse IP le client souhaite-t-il recevoir
Longueur d'option4Déc1
Valeur d'option172.16.134.61String4
Numéro d'option551Les 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'option81
Valeur d'option01: 03: 06: 0c: 0f: 1c: 42: 798
Numéro d'option82DécOption 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'option18Déc
Valeur d'option01:06
00: 04: 00: 01: 00: 04
02:08
00: 06: c8: soit: 19: 93: 11: 48
Hex
Fin du colis255Déc1255 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 DHCPOFFER
Position du paquetNom de la valeur (commun)ExempleSoumissionOctetExplication
1Demande de démarrage1Hex1Type de message. 1 - demande du client au serveur, 2 - réponse du serveur au client
2Type de matériel1Hex1Type d'adresse matérielle, dans ce protocole 1 - MAC
3Le matériel adhère à la longueur6Hex1Longueur de l'adresse MAC de l'appareil
4Le houblon1Hex1Nombre de routes intermédiaires
5Identifiant de transaction23: cf: de: 1dHex4Identifiant de transaction unique. Généré par le client au début de l'opération de demande
7Deuxième écoulé0Hex4Temps en secondes depuis le début du processus d'obtention de l'adresse
9Drapeaux Bootp0Hex2Quelques 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
11Adresse IP du client0.0.0.0String4Adresse IP du client (le cas échéant)
15L'adresse IP de votre client172.16.134.61String4Adresse IP proposée par le serveur (le cas échéant)
19Adresse IP du serveur suivant0.0.0.0String4Adresse IP du serveur (si connue)
23Adresse IP de l'agent relais172.16.114.41String4Adresse IP de l'agent relais (par exemple, un commutateur)
27Adresse MAC du client14: d6: 4d: a7: c9: 55Hex6Adresse MAC de l'expéditeur du paquet (client)
31Remplissage d'adresse du matériel clientHex10Place réservée. Généralement des zéros
41Nom d'hôte du serveurString64Le nom du serveur DHCP. Habituellement non transmis
105Nom du fichier de démarrageString128Le nom de fichier sur le serveur utilisé par les stations sans disque lors du démarrage
235Cookie magique63: 82: 53: 63Hex4Le 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
236Numéro d'option53Déc1Option 53 spécifiant le type de paquet DHCP 2 - DHCPOFFER
Longueur d'option1Déc1
Valeur d'option2Déc1
Numéro d'option1Déc1Option offrant un masque de réseau client DHCP
Longueur d'option4Déc1
Valeur d'option255.255.224.0String4
Numéro d'option3Déc1Option offrant la passerelle par défaut du client DHCP
Longueur d'option4Déc1
Valeur d'option172.16.12.1String4
Numéro d'option6Déc1Option offrant DHCP au client DNS
Longueur d'option4Déc1
Valeur d'option8.8.8.8String4
Numéro d'option51Déc1La durée de vie des paramètres réseau émis en secondes, après quoi le client DHCP doit les demander à nouveau
Longueur d'option4Déc1
Valeur d'option86400Déc4
Numéro d'option82Déc1L'option 82, répète ce qui est arrivé dans DHCPDISCOVER
Longueur d'option18Déc1
Valeur d'option01: 08: 00: 06: 00
01: 01: 00: 00: 01
02: 06: 00: 03: 0f
26: 4d: ec
Déc18
Fin du colis255Déc1255 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 DHCPREQUEST
Position du paquetNom de la valeur (commun)ExempleSoumissionOctetExplication
1Demande de démarrage1Hex1Type de message. 1 - demande du client au serveur, 2 - réponse du serveur au client
2Type de matériel1Hex1Type d'adresse matérielle, dans ce protocole 1 - MAC
3Le matériel adhère à la longueur6Hex1Longueur de l'adresse MAC de l'appareil
4Le houblon1Hex1Nombre de routes intermédiaires
5Identifiant de transaction23: cf: de: 1dHex4Identifiant de transaction unique. Généré par le client au début de l'opération de demande
7Deuxième écoulé0Hex4Temps en secondes depuis le début du processus d'obtention de l'adresse
9Drapeaux Bootp8000Hex2Quelques drapeaux qui peuvent être définis comme une indication des paramètres du protocole. Dans ce cas, "Diffusion"
11Adresse IP du client0.0.0.0String4Adresse IP du client (le cas échéant)
15L'adresse IP de votre client172.16.134.61String4Adresse IP proposée par le serveur (le cas échéant)
19Adresse IP du serveur suivant0.0.0.0String4Adresse IP du serveur (si connue)
23Adresse IP de l'agent relais172.16.114.41String4Adresse IP de l'agent relais (par exemple, un commutateur)
27Adresse MAC du client14: d6: 4d: a7: c9: 55Hex6Adresse MAC de l'expéditeur du paquet (client)
31Remplissage d'adresse du matériel clientHex10Place réservée. Généralement des zéros
41Nom d'hôte du serveurString64Le nom du serveur DHCP. Habituellement non transmis
105Nom du fichier de démarrageString128Le nom de fichier sur le serveur utilisé par les stations sans disque lors du démarrage
235Cookie magique63: 82: 53: 63Hex4Le 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
236Numéro d'option53Déc3Option 53 spécifiant le type de paquet DHCP 3 - DHCPREQUEST
Longueur d'option1Déc1
Valeur d'option3Déc1
Numéro d'option61Déc1ID client: 01 (pour Ethernet) + adresse MAC du client
Longueur d'option7Déc1
Valeur d'option01: 2c: ab: 25: ff: 72: a6Hex7
Numéro d'option60Dé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'option11Déc
Valeur d'optionudhcp 0.9.8String
Numéro d'option551Les 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'option81
Valeur d'option01: 03: 06: 0c: 0f: 1c: 42: 798
Numéro d'option82Déc1L'option 82, répète ce qui est arrivé dans DHCPDISCOVER
Longueur d'option18Déc1
Valeur d'option01: 08: 00: 06: 00
01: 01: 00: 00: 01
02: 06: 00: 03: 0f
26: 4d: ec
Déc18
Fin du colis255Déc1255 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 DHCPACK
Position du paquetNom de la valeur (commun)ExempleSoumissionOctetExplication
1Demande de démarrage2Hex1Type de message. 1 - demande du client au serveur, 2 - réponse du serveur au client
2Type de matériel1Hex1Type d'adresse matérielle, dans ce protocole 1 - MAC
3Le matériel adhère à la longueur6Hex1Longueur de l'adresse MAC de l'appareil
4Le houblon1Hex1Nombre de routes intermédiaires
5Identifiant de transaction23: cf: de: 1dHex4Identifiant de transaction unique. Généré par le client au début de l'opération de demande
7Deuxième écoulé0Hex4Temps en secondes depuis le début du processus d'obtention de l'adresse
9Drapeaux Bootp8000Hex2Quelques drapeaux qui peuvent être définis comme une indication des paramètres du protocole. Dans ce cas, "Diffusion"
11Adresse IP du client0.0.0.0String4Adresse IP du client (le cas échéant)
15L'adresse IP de votre client172.16.134.61String4Adresse IP proposée par le serveur (le cas échéant)
19Adresse IP du serveur suivant0.0.0.0String4Adresse IP du serveur (si connue)
23Adresse IP de l'agent relais172.16.114.41String4Adresse IP de l'agent relais (par exemple, un commutateur)
27Adresse MAC du client14: d6: 4d: a7: c9: 55Hex6Adresse MAC de l'expéditeur du paquet (client)
31Remplissage d'adresse du matériel clientHex10Place réservée. Généralement des zéros
41Nom d'hôte du serveurString64Le nom du serveur DHCP. Habituellement non transmis
105Nom du fichier de démarrageString128Le nom de fichier sur le serveur utilisé par les stations sans disque lors du démarrage
235Cookie magique63: 82: 53: 63Hex4Le 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
236Numéro d'option53Déc3Option 53 spécifiant le type de paquet DHCP 5 - DHCPACK
Longueur d'option1Déc1
Valeur d'option5Déc1
Numéro d'option1Déc1Option offrant un masque de réseau client DHCP
Longueur d'option4Déc1
Valeur d'option255.255.224.0String4
Numéro d'option3Déc1Option offrant la passerelle par défaut du client DHCP
Longueur d'option4Déc1
Valeur d'option172.16.12.1String4
Numéro d'option6Déc1Option offrant DHCP au client DNS
Longueur d'option4Déc1
Valeur d'option8.8.8.8String4
Numéro d'option51Déc1La durée de vie des paramètres réseau émis en secondes, après quoi le client DHCP doit les demander à nouveau
Longueur d'option4Déc1
Valeur d'option86400Déc4
Numéro d'option82Déc1L'option 82, répète ce qui est arrivé dans DHCPDISCOVER
Longueur d'option18Déc1
Valeur d'option01: 08: 00: 06: 00
01: 01: 00: 00: 01
02: 06: 00: 03: 0f
26: 4d: ec
Déc18
Fin du colis255Déc1255 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 GitHub

Section 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;)

Source: https://habr.com/ru/post/fr445338/


All Articles