
Ziel dieses Projekts war:
- Studium des DHCP-Protokolls im IPv4-Netzwerk
- Studium von Python (ein wenig mehr als von Grund auf 😉 )
- Ersatz für den Server (mein Fork), Original , das unter einem neuen Betriebssystem immer schwieriger zu kompilieren ist. Außerdem gefällt mir nicht, dass ich das Binary nicht 'sofort' austauschen kann.
- Erstellung eines betriebsbereiten DHCP-Servers mit der Möglichkeit, die IP-Adresse des Clients anhand der MAC-Adresse des Clients oder der Kombination aus MAC des Switches + Port (Option 82) auszuwählen.
- Schreiben eines weiteren Fahrrads (Oh! Das ist mein Lieblingsbeschäftigung)
- Erhalt von Kritik zu meiner Ungeschicklichkeit auf Habrhabr (oder besser noch eine Einladung) 😉
Ergebnis: funktioniert 😉 Erprobt auf FreeBSD und Ubuntu. Theoretisch könnte der Code auch unter jedem anderen Betriebssystem verwendet werden, da es anscheinend keine spezifischen Bindungen im Code gibt.
Achtung! Jetzt wird es viel.
Link zum Repository für Liebhaber .
Der Prozess der Installation, Konfiguration und Verwendung des Ergebnisses des ‚Studiums der Materie‘ befindet sich viel tiefer, und dann etwas Theorie zum DHCP-Protokoll. Für mich. Und für die Geschichte 😉
Ein wenig Theorie
Was ist DHCP
Das ist ein Netzwerkprotokoll, das einem Gerät ermöglicht, seine IP-Adresse (und andere Parameter wie Gateway, DNS und mehr) vom DHCP-Server zu erfahren. Der Austausch von Paketen erfolgt über das UDP-Protokoll. Das allgemeine Arbeitsprinzip eines Geräts bei der Anforderung von Netzparameter ist wie folgt:
- Das Gerät (Client) sendet eine Broadcast-UDP-Anfrage (DHCPDISCOVER) im gesamten Netzwerk mit der Anfrage: ‚Na, jemand, gebt mir eine IP-Adresse‘. Üblicherweise (aber nicht immer) erfolgt die Anfrage von Port 68 (Quelle), während die Zieladresse Port 67 ist (Ziel). Einige Geräte senden Pakete auch von Port 67. Innerhalb des DHCPDISCOVER-Pakets ist die MAC-Adresse des Client-Geräts enthalten.
- Alle DHCP-Server im Netzwerk (und es können mehrere sein) formulieren ein Angebot (DHCPOFFER) mit Netzwerkeinstellungen für das Gerät, das DHCPDISCOVER gesendet hat, und senden es ebenfalls als Broadcast im Netzwerk. Die Identifizierung, an wen dieses Paket gerichtet ist, erfolgt über die MAC-Adresse des Clients, die zuvor in der DHCPDISCOVER-Anfrage bereitgestellt wurde.
- Der Client empfängt Pakete mit Angeboten für Netzwerkkonfigurationen, wählt die attraktivste aus (die Kriterien können unterschiedlich sein, beispielsweise auch die Lieferzeit des Pakets oder die Anzahl der Zwischennetzwerke), und sendet an den bevorzugten DHCP-Server eine offizielle DHCP-Anfrage DHCPREQUEST mit den Netzwerkkonfigurationen. In diesem Fall geht das Paket bereits an einen bestimmten DHCP-Server.
- Der Server, der die DHCPREQUEST erhalten hat, sendet ein DHCPACK-Paket, in dem erneut die für diesen Client vorgesehenen Netzwerkkonfigurationen aufgeführt sind.

Darüber hinaus gibt es DHCPINFORM-Pakete, die vom Client gesendet werden und deren Ziel es ist, den DHCP-Server darüber zu informieren, dass der "Client aktiv" ist und die ausgegebenen Netzwerkkonfigurationen nutzt. In dieser Serverimplementierung werden diese Pakete ignoriert.
Paketformat
Im Allgemeinen sieht das Ethernet-Paket folgendermaßen aus:
![]()
In unserem Fall betrachten wir nur die Daten des UDP-Paketinhalts, ohne die Header der OSI-Protokolle, nämlich die Struktur von DHCP:
DHCPDISCOVER
Der Prozess zum Erhalt einer IP-Adresse für ein Gerät beginnt damit, dass der DHCP-Client eine Broadcast-Anfrage von Port 68 an 255.255.255.255:67 sendet. In diesem Paket gibt der Client seine MAC-Adresse an und auch, was genau er vom DHCP-Server erhalten möchte. Die Paketstruktur wird in der nachstehenden Tabelle beschrieben.
Tabelle der Paketstruktur DHCPDISCOVER
Position im Paket
Name des Wertes
Beispiel
Darstellung
Byte
Erläuterung
1
Boot Anfrage
1
Hex
1
Nachrichtentyp. 1 — Anfrage vom Client an den Server, 2 — Antwort vom Server an den Client
2
Hardwaretyp
1
Hex
1
Typ der Hardwareadresse, in diesem Protokoll 1 — MAC
3
Länge der Hardwareadresse
6
Hex
1
Länge der MAC-Adresse des Geräts
4
Hops
1
Hex
1
Anzahl der Zwischennetzwerke
5
Transaktions-ID
23:cf:de:1d
Hex
4
Eindeutige Transaktions-ID. Wird vom Client zu Beginn der Anforderungsoperation generiert.
7
Verstrichene Zeit in Sekunden
0
Hex
4
Zeit in Sekunden seit Beginn des Prozesses zur Erlangung der Adresse
9
Bootp-Flags
0
Hex
2
Einige Flags, die gesetzt werden können, um die Protokollparameter anzugeben
11
Client-IP-Adresse
0.0.0.0
Zeichenkette
4
Die IP-Adresse des Clients (falls vorhanden)
15
Ihre Client-IP-Adresse
0.0.0.0
Zeichenkette
4
Die vom Server vorgeschlagene IP-Adresse (falls vorhanden)
19
Nächste Server-IP-Adresse
0.0.0.0
Zeichenkette
4
IP-Adresse des Servers (falls bekannt)
23
Relay-Agent-IP-Adresse
172.16.114.41
Zeichenkette
4
IP-Adresse des Relay-Agenten (z. B. Switch)
27
Client-MAC-Adresse
14:d6:4d:a7:c9:55
Hex
6
MAC-Adresse des Paketabsenders (Client)
31
Client-Hardwareadressenausfüllung
Hex
10
Reservierter Platz. Normalerweise mit Nullen gefüllt.
41
Server-Hostname
Zeichenkette
64
Name des DHCP-Servers. Normalerweise wird dies nicht übertragen.
105
Boot-Dateiname
Zeichenkette
128
Dateiname auf dem Server, der von diskless Stations bei der Anmeldung verwendet wird
235
Magic-Cookie
63:82:53:63
Hex
4
Die "magische" Zahl, anhand derer unter anderem bestimmt werden kann, dass dieses Paket dem DHCP-Protokoll gehört
DHCP-Optionen. Können in beliebiger Reihenfolge auftreten
236
Optionsnummer
53
Dez
1
Option 53, die den Typ des DHCP-Pakets definiert
1 — DHCPDISCOVER
3 — DHCPREQUEST
2 — DHCPOFFER
5 — DHCPACK
8 — DHCPINFORM
Länge der Option
1
Dez
1
Wert der Option
1
Dez
1
Optionsnummer
50
Dez
1
Welche IP-Adresse möchte der Client erhalten
Länge der Option
4
Dez
1
Wert der Option
172.16.134.61
Zeichenkette
4
Optionsnummer
55
1
Von dem Client angeforderte Netzwerkkonfigurationen. Die Zusammensetzung kann unterschiedlich sein
01 — Subnetzmaske
03 — Gateway
06 — DNS
oc — Hostname
0f — Netzwerkdomänenname
1c — Broadcast-Adresse (Brodcast)
42 — TFTP-Servername
79 — Classless Static Route
Länge der Option
8
1
Wert der Option
01:03:06:0c:0f:1c:42:79
8
Optionsnummer
82
Dez
Option 82, die die MAC-Adresse des Gerätes — des Relais und einige zusätzliche Werte überträgt.
In der Regel — Port des Switches, an dem der Endclient für DHCP arbeitet. In dieser Option sind zusätzliche Parameter "eingebettet". Das erste Byte ist die "Suboptionsnummer", das zweite ihre Länge, danach der Wert.
In diesem Fall sind in Option 82 Suboptionen eingebettet:
Agent Circuit ID = 00:04:00:01:00:04, wobei die letzten zwei Bytes der Port des DHCP-Clients sind, von dem die Anfrage kam
Agent Remote ID = 00:06:c8:be:19:93:11:48 — MAC-Adresse des Relaisgerätes für DHCP
Länge der Option
18
Dez
Wert der Option
01:06
00:04:00:01:00:04
02:08
00:06:c8:be:19:93:11:48
Hex
Ende des Pakets
255
Dez
1
255 symbolisiert das Ende des Pakets
DHCPOFFER
Sobald der Server das Paket DHCPDISCOVER erhält und sieht, dass er dem Client etwas aus der Anfrage anbieten kann, erstellt er eine Antwort – DHCPDISCOVER. Die Antwort wird an den Port gesendet, von dem sie kam, broadcast-mäßig, da der Client zu diesem Zeitpunkt noch keine IP-Adresse hat und das Paket daher nur akzeptieren kann, wenn es broadcast gesendet wird. Der Client erkennt, dass dies sein Paket ist, anhand seiner MAC-Adresse im Paket sowie der Transaktionsnummer, die er beim Erstellen des ersten Pakets generiert.
Tabelle der Paketstruktur DHCPOFFER
Position im Paket
Bezeichnung des Wertes (üblich)
Beispiel
Darstellung
Byte
Erläuterung
1
Boot Anfrage
1
Hex
1
Nachrichtentyp. 1 — Anfrage vom Client an den Server, 2 — Antwort vom Server an den Client
2
Hardwaretyp
1
Hex
1
Typ der Hardwareadresse, in diesem Protokoll 1 — MAC
3
Länge der Hardwareadresse
6
Hex
1
Länge der MAC-Adresse des Geräts
4
Hops
1
Hex
1
Anzahl der Zwischennetzwerke
5
Transaktions-ID
23:cf:de:1d
Hex
4
Eindeutige Transaktions-ID. Wird vom Client zu Beginn der Anforderungsoperation generiert.
7
Verstrichene Zeit in Sekunden
0
Hex
4
Zeit in Sekunden seit Beginn des Prozesses zur Erlangung der Adresse
9
Bootp-Flags
0
Hex
2
Einige Flags, die gesetzt werden können, um Parameter des Protokolls anzuzeigen. In diesem Fall bedeutet 0 — Anfrage Typ Unicast
11
Client-IP-Adresse
0.0.0.0
Zeichenkette
4
Die IP-Adresse des Clients (falls vorhanden)
15
Ihre Client-IP-Adresse
172.16.134.61
Zeichenkette
4
Die vom Server vorgeschlagene IP-Adresse (falls vorhanden)
19
Nächste Server-IP-Adresse
0.0.0.0
Zeichenkette
4
IP-Adresse des Servers (falls bekannt)
23
Relay-Agent-IP-Adresse
172.16.114.41
Zeichenkette
4
IP-Adresse des Relay-Agenten (z. B. Switch)
27
Client-MAC-Adresse
14:d6:4d:a7:c9:55
Hex
6
MAC-Adresse des Paketabsenders (Client)
31
Client-Hardwareadressenausfüllung
Hex
10
Reservierter Platz. Normalerweise mit Nullen gefüllt.
41
Server-Hostname
Zeichenkette
64
Name des DHCP-Servers. Normalerweise wird dies nicht übertragen.
105
Boot-Dateiname
Zeichenkette
128
Dateiname auf dem Server, der von diskless Stations bei der Anmeldung verwendet wird
235
Magic-Cookie
63:82:53:63
Hex
4
Die "magische" Zahl, anhand derer unter anderem bestimmt werden kann, dass dieses Paket dem DHCP-Protokoll gehört
DHCP-Optionen. Können in beliebiger Reihenfolge auftreten
236
Optionsnummer
53
Dez
1
Option 53, die den Typ des DHCP-Pakets definiert 2 — DHCPOFFER
Länge der Option
1
Dez
1
Wert der Option
2
Dez
1
Optionsnummer
1
Dez
1
Option, die dem DHCP-Client eine Subnetzmaske anbietet
Länge der Option
4
Dez
1
Wert der Option
255.255.224.0
Zeichenkette
4
Optionsnummer
3
Dez
1
Option, die dem DHCP-Client ein Standardgateway anbietet
Länge der Option
4
Dez
1
Wert der Option
172.16.12.1
Zeichenkette
4
Optionsnummer
6
Dez
1
Option, die dem DHCP-Client einen DNS anbietet
Länge der Option
4
Dez
1
Wert der Option
8.8.8.8
Zeichenkette
4
Optionsnummer
51
Dez
1
Die Lebensdauer der vergebenen Netzwerkkonfigurationen in Sekunden, nach denen der DHCP-Client diese erneut anfordern muss
Länge der Option
4
Dez
1
Wert der Option
86400
Dez
4
Optionsnummer
82
Dez
1
Option 82, die wiederholt, was im DHCPDISCOVER empfangen wurde
Länge der Option
18
Dez
1
Wert der Option
01:08:00:06:00
01:01:00:00:01
02:06:00:03:0f
26:4d:ec
Dez
18
Ende des Pakets
255
Dez
1
255 symbolisiert das Ende des Pakets
DHCPREQUEST
Nachdem der Client das DHCPOFFER erhalten hat, erstellt er ein Paket mit der Anfrage der Netzwerkeinstellungen nicht für alle DHCP-Server im Netzwerk, sondern nur für einen bestimmten, dessen DHCPOFFER ihm am meisten "gefiel". Die Kriterien, die das "Gefallen" bestimmen, können unterschiedlich sein und hängen von der Implementierung des DHCP-Clients ab. Der Empfänger der Anfrage wird über die MAC-Adresse des DHCP-Servers angegeben. Das DHCPREQUEST-Paket kann auch vom Client ohne vorherige Dhcpdiscover-Anfrage gesendet werden, wenn die IP-Adresse vom Server zuvor bereits erhalten wurde.
Tabelle der Struktur des DHCPREQUEST-Pakets
Position im Paket
Bezeichnung des Wertes (üblich)
Beispiel
Darstellung
Byte
Erläuterung
1
Boot Anfrage
1
Hex
1
Nachrichtentyp. 1 — Anfrage vom Client an den Server, 2 — Antwort vom Server an den Client
2
Hardwaretyp
1
Hex
1
Typ der Hardwareadresse, in diesem Protokoll 1 — MAC
3
Länge der Hardwareadresse
6
Hex
1
Länge der MAC-Adresse des Geräts
4
Hops
1
Hex
1
Anzahl der Zwischennetzwerke
5
Transaktions-ID
23:cf:de:1d
Hex
4
Eindeutige Transaktions-ID. Wird vom Client zu Beginn der Anforderungsoperation generiert.
7
Verstrichene Zeit in Sekunden
0
Hex
4
Zeit in Sekunden seit Beginn des Prozesses zur Erlangung der Adresse
9
Bootp-Flags
8000
Hex
2
Einige Flags, die gesetzt werden können, um die Parameter des Protokolls anzugeben. In diesem Fall ist es auf "Broadcast" gesetzt.
11
Client-IP-Adresse
0.0.0.0
Zeichenkette
4
Die IP-Adresse des Clients (falls vorhanden)
15
Ihre Client-IP-Adresse
172.16.134.61
Zeichenkette
4
Die vom Server vorgeschlagene IP-Adresse (falls vorhanden)
19
Nächste Server-IP-Adresse
0.0.0.0
Zeichenkette
4
IP-Adresse des Servers (falls bekannt)
23
Relay-Agent-IP-Adresse
172.16.114.41
Zeichenkette
4
IP-Adresse des Relay-Agenten (z. B. Switch)
27
Client-MAC-Adresse
14:d6:4d:a7:c9:55
Hex
6
MAC-Adresse des Paketabsenders (Client)
31
Client-Hardwareadressenausfüllung
Hex
10
Reservierter Platz. Normalerweise mit Nullen gefüllt.
41
Server-Hostname
Zeichenkette
64
Name des DHCP-Servers. Normalerweise wird dies nicht übertragen.
105
Boot-Dateiname
Zeichenkette
128
Dateiname auf dem Server, der von diskless Stations bei der Anmeldung verwendet wird
235
Magic-Cookie
63:82:53:63
Hex
4
Die "magische" Zahl, anhand derer unter anderem bestimmt werden kann, dass dieses Paket dem DHCP-Protokoll gehört
DHCP-Optionen. Können in beliebiger Reihenfolge auftreten
236
Optionsnummer
53
Dez
3
Option 53, die den Typ des DHCP-Pakets 3 – DHCPREQUEST definiert
Länge der Option
1
Dez
1
Wert der Option
3
Dez
1
Optionsnummer
61
Dez
1
Kundenkennung: 01 (für Ethernet) + MAC-Adresse des Clients
Länge der Option
7
Dez
1
Wert der Option
01:2c:ab:25:ff:72:a6
Hex
7
Optionsnummer
60
Dez
"Vendor class identifier". In meinem Fall gibt es die Version des DHCP-Clients an. Möglicherweise geben andere Geräte etwas anderes zurück. Windows beispielsweise meldet MSFT 5.0.
Länge der Option
11
Dez
Wert der Option
udhcp 0.9.8
Zeichenkette
Optionsnummer
55
1
Von dem Client angeforderte Netzwerkkonfigurationen. Die Zusammensetzung kann unterschiedlich sein
01 — Subnetzmaske
03 — Gateway
06 — DNS
oc — Hostname
0f — Netzwerkdomänenname
1c — Broadcast-Adresse (Brodcast)
42 — TFTP-Servername
79 — Classless Static Route
Länge der Option
8
1
Wert der Option
01:03:06:0c:0f:1c:42:79
8
Optionsnummer
82
Dez
1
Option 82, die wiederholt, was im DHCPDISCOVER empfangen wurde
Länge der Option
18
Dez
1
Wert der Option
01:08:00:06:00
01:01:00:00:01
02:06:00:03:0f
26:4d:ec
Dez
18
Ende des Pakets
255
Dez
1
255 symbolisiert das Ende des Pakets
DHCPACK
Als Bestätigung, dass "ja, das ist deine IP-Adresse, und ich werde sie niemand anderem geben", dient das Paket im Format DHCPACK vom DHCP-Server an den Client. Es wird, wie die anderen Pakete, ebenfalls broadcasted. Allerdings dupliziere ich im folgenden Code des in Python implementierten DHCP-Servers vorsichtshalber jede Broadcast-Anfrage, indem ich ein Paket an eine bestimmte IP-Adresse des Clients sende, wenn diese bereits bekannt ist. Der DHCP-Server interessiert sich jedoch überhaupt nicht dafür, ob das DHCPACK-Paket den Client erreicht hat. Erhält der Client kein DHCPACK, wiederholt er einfach nach einer gewissen Zeit das DHCPREQUEST.
Tabelle der Struktur des DHCPACK-Pakets
Position im Paket
Bezeichnung des Wertes (üblich)
Beispiel
Darstellung
Byte
Erläuterung
1
Boot Anfrage
2
Hex
1
Nachrichtentyp. 1 — Anfrage vom Client an den Server, 2 — Antwort vom Server an den Client
2
Hardwaretyp
1
Hex
1
Typ der Hardwareadresse, in diesem Protokoll 1 — MAC
3
Länge der Hardwareadresse
6
Hex
1
Länge der MAC-Adresse des Geräts
4
Hops
1
Hex
1
Anzahl der Zwischennetzwerke
5
Transaktions-ID
23:cf:de:1d
Hex
4
Eindeutige Transaktions-ID. Wird vom Client zu Beginn der Anforderungsoperation generiert.
7
Verstrichene Zeit in Sekunden
0
Hex
4
Zeit in Sekunden seit Beginn des Prozesses zur Erlangung der Adresse
9
Bootp-Flags
8000
Hex
2
Einige Flags, die gesetzt werden können, um die Parameter des Protokolls anzugeben. In diesem Fall ist es auf "Broadcast" gesetzt.
11
Client-IP-Adresse
0.0.0.0
Zeichenkette
4
Die IP-Adresse des Clients (falls vorhanden)
15
Ihre Client-IP-Adresse
172.16.134.61
Zeichenkette
4
Die vom Server vorgeschlagene IP-Adresse (falls vorhanden)
19
Nächste Server-IP-Adresse
0.0.0.0
Zeichenkette
4
IP-Adresse des Servers (falls bekannt)
23
Relay-Agent-IP-Adresse
172.16.114.41
Zeichenkette
4
IP-Adresse des Relay-Agenten (z. B. Switch)
27
Client-MAC-Adresse
14:d6:4d:a7:c9:55
Hex
6
MAC-Adresse des Paketabsenders (Client)
31
Client-Hardwareadressenausfüllung
Hex
10
Reservierter Platz. Normalerweise mit Nullen gefüllt.
41
Server-Hostname
Zeichenkette
64
Name des DHCP-Servers. Normalerweise wird dies nicht übertragen.
105
Boot-Dateiname
Zeichenkette
128
Dateiname auf dem Server, der von diskless Stations bei der Anmeldung verwendet wird
235
Magic-Cookie
63:82:53:63
Hex
4
Die "magische" Zahl, anhand derer unter anderem bestimmt werden kann, dass dieses Paket dem DHCP-Protokoll gehört
DHCP-Optionen. Können in beliebiger Reihenfolge auftreten
236
Optionsnummer
53
Dez
3
Option 53, die den Typ des DHCP-Pakets 5 – DHCPACK definiert
Länge der Option
1
Dez
1
Wert der Option
5
Dez
1
Optionsnummer
1
Dez
1
Option, die dem DHCP-Client eine Subnetzmaske anbietet
Länge der Option
4
Dez
1
Wert der Option
255.255.224.0
Zeichenkette
4
Optionsnummer
3
Dez
1
Option, die dem DHCP-Client ein Standardgateway anbietet
Länge der Option
4
Dez
1
Wert der Option
172.16.12.1
Zeichenkette
4
Optionsnummer
6
Dez
1
Option, die dem DHCP-Client einen DNS anbietet
Länge der Option
4
Dez
1
Wert der Option
8.8.8.8
Zeichenkette
4
Optionsnummer
51
Dez
1
Die Lebensdauer der vergebenen Netzwerkkonfigurationen in Sekunden, nach denen der DHCP-Client diese erneut anfordern muss
Länge der Option
4
Dez
1
Wert der Option
86400
Dez
4
Optionsnummer
82
Dez
1
Option 82, die wiederholt, was im DHCPDISCOVER empfangen wurde
Länge der Option
18
Dez
1
Wert der Option
01:08:00:06:00
01:01:00:00:01
02:06:00:03:0f
26:4d:ec
Dez
18
Ende des Pakets
255
Dez
1
255 symbolisiert das Ende des Pakets
Installation
Die Einrichtung besteht tatsächlich darin, die benötigten Python-Module zu installieren. Es wird vorausgesetzt, dass MySQL bereits installiert und konfiguriert ist.
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
Wir erstellen eine MySQL-Datenbank, laden das Dump pydhcp.sql hoch und konfigurieren die Konfigurationsdatei.
Konfiguration
Alle Servereinstellungen befinden sich in einer XML-Datei. Referenzdatei:
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')Jetzt detaillierter zu den Tags:
Der Abschnitt dhcpserver beschreibt die grundlegenden Einstellungen zum Starten des Servers, nämlich:
- host — welche IP-Adresse der Server am Port 67 abfängt.
- broadcast — welche IP-Adresse für DHCPOFFER und DHCPACK verwendet wird.
- DHCPServer — welche IP-Adresse der DHCP-Server hat.
- LeaseTime — die Zeit, für die die zugewiesene IP-Adresse gültig ist.
- ThreadLimit — wie viele Threads gleichzeitig zur Verarbeitung der eingehenden UDP-Pakete am Port 67 aktiv sind. Es wird angenommen, dass dies bei stark ausgelasteten Projekten hilft 😉.
- defaultMask, defaultRouter, defaultDNS — die Standardwerte, die dem Abonnenten angeboten werden, wenn die IP in der Datenbank gefunden wird, aber keine zusätzlichen Parameter angegeben sind.
Abschnitt mysql:
host, username, password, basename — alles erklärt sich von selbst. Eine Beispielform der Datenbankstruktur wird bereitgestellt auf
Abschnitt query: Hier sind die Abfragen zur Beschaffung von OFFER/ACK beschrieben:
- offer_count — die Anzahl der Zeilen mit Anfragen, die ein Ergebnis in der Form ip, mask, router, dns zurückgeben.
- offer_n — Anfragenzeile. Wenn die Rückgabe leer ist, wird die nächste Anfrage offer ausgeführt.
- history_sql — eine Abfrage, die beispielsweise in die 'Authentifizierungshistorie' für den Abonnenten schreibt.
In den Abfragen können beliebige Variablen aus dem Abschnitt options oder Optionen aus dem DHCP-Protokoll verwendet werden.
Abschnitt options. Hier wird es interessanter. Hier können wir Variablen erstellen, die wir später im Abschnitt query verwenden können.
Zum Beispiel:
option_82_hex:sw_port1:20:22, dieser Befehl nimmt die gesamte Zeile aus der DHCP-Anfrage Option 82 im Hex-Format im Bereich von Byte 20 bis 22 (einschließlich) und legt sie in einer neuen Variablen sw_port1 ab (Switch-Port, von dem die Anfrage kam)
option_82_hex:sw_mac:26:40, wir definieren die Variable sw_mac, indem wir den Hex-Wert aus dem Bereich 26:40 nehmen
Um alle möglichen Optionen zu sehen, die in Anfragen verwendet werden können, kann der Server mit dem Schlüssel -d gestartet werden. Wir sehen etwa folgendes Protokoll:
--es kam ein Paket DHCPINFORM an Port 67 von 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,
'ReqListRouterDiscover': 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'}Daher können wir jede Variable in {} einschließen, und sie wird im SQL-Abfrage verwendet.
Lassen Sie uns zur Dokumentation festhalten, welche IP-Adresse der Client erhalten hat:


Server starten
.\/pydhcpdb.py -d -c config.xml
— d Modus für DEBUG-Ausgabe in der Konsole
— c Konfigurationsdatei
Überprüfung der Funktionsweise
Nun also ausführlicher zur Umsetzung des Servers in Python. Das ist schmerzhaft. Python wurde "on the fly" gelernt. Viele Aspekte wurden nach dem Motto umgesetzt: "Wow, das habe ich irgendwie gemacht, es funktioniert." Völlig unoptimiert und in dieser Form belassen, hauptsächlich wegen geringer Erfahrung in der Python-Entwicklung. Ich werde mich auf die interessantesten Aspekte der Server-Implementierung im "Code" konzentrieren.
Parser für die XML-Konfigurationsdatei
Es wird das Standardmodul Python xml.dom verwendet. Es scheint einfach zu sein, aber bei der Implementierung hat es merklich an brauchbarer Dokumentation und Beispielen im Netz gefehlt, die dieses Modul verwenden.
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
Es ist überraschend, dass Multithreading in Python sehr klar und einfach umgesetzt ist.
def PacketWork(data, addr):
...
# Implementierung der Analyse des eingehenden Pakets und der Antwort darauf
...
while True:
data, addr = udp_socket.recvfrom(1024) # Warten auf ein UDP-Paket
thread = threading.Thread(target=PacketWork, args=(data, addr,)).start() # Sobald es ankommt, starten wir die vorher definierte Funktion PacketWork im Hintergrund mit den Parametern
while threading.active_count() > gconfig["dhcp_ThreadLimit"]:
time.sleep(1) # Wenn die Anzahl der bereits gestarteten Threads höher ist als in den Einstellungen, warten wir, bis es weniger werdenEmpfang/Versand von DHCP-Paketen
Um UDP-Pakete, die über die Netzwerkkarte gehen, abzufangen, muss der Socket "hochgefahren" werden:
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) udp_socket.bind((gconfig["dhcp_host"], 67))
, wobei die Flags:
- AF_INET bedeutet, dass das Adressformat IP:Port sein wird. Es kann auch AF_UNIX geben — wobei die Adresse durch einen Dateinamen angegeben wird.
- SOCK_DGRAM bedeutet, dass wir kein „rohes Paket“ empfangen, sondern bereits ein Paket, das durch die Firewall gefiltert und teilweise abgeschnitten wurde. Das heißt, wir erhalten nur das UDP-Paket ohne die „physische“ Verpackung des UDP-Pakets. Wenn das FLAG SOCK_RAW verwendet wird, muss auch diese „Verpackung“ geparst werden.
Der Versand eines Pakets kann sowohl per Broadcast erfolgen:
udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) #legen den Socket in den Broadcast-Modus
rz=udp_socket.sendto(packetack, (gconfig["broadcast"],68)), als auch an die Adresse, „von der das Paket kam“:
udp_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #legen den Socket in den Modus "mehrere Zuhörer"
rz=udp_socket.sendto(packetack, addr), wobei SOL_SOCKET „Protokollebene“ zum Setzen der Optionen bedeutet,
, SO_BROADCAST die Option ist, dass das Paket „per Broadcast“ gesendet wird,
, SO_REUSEADDR ist die Option, die den Socket in den Modus „viele Zuhörer“ wechselt. Theoretisch ist sie in diesem Fall nicht erforderlich, aber auf einem der FreeBSD-Server, auf dem ich getestet habe, funktionierte der Code ohne diese Option nicht.
Analyse des DHCP-Pakets
Hier hat mir Python wirklich gefallen. Es stellt sich heraus, dass es aus der „Box“ recht flexibel mit Bytecode umgehen kann. Es ermöglicht, diesen sehr einfach in dezimale Werte, Strings und Hex zu übersetzen – also genau das, was wir brauchen, um die Struktur des Pakets zu verstehen. So kann man beispielsweise einen Bereich von Bytes in HEX und einfach in Bytes erhalten:
res["xidhex"]=data[4:8].hex()
res["xidbyte"]=data[4:8], die Bytes in eine Struktur packen:
res["flags"]=pack('BB',data[10],data[11])Die IP aus der Struktur erhalten:
res["ciaddr"]=socket.inet_ntoa(pack('BBBB',data[12],data[13],data[14],data[15]));Und umgekehrt:
res=res+socket.inet_pton(socket.AF_INET, gconfig["dhcp_Server"])
Das ist alles 😉
Quelle: habr.com
