
Obiectivul acestui proiect a fost:
- Studiul protocolului DHCP în lucrul cu rețeaua IPv4
- Studiul Python (puțin mai mult decât de la zero 😉 )
- înlocuirea serverului (fork-ul meu), originalul , care devine tot mai greu de compilat pentru un nou sistem de operare. Și nu-mi place că binarul, pe care nu-l pot „schimba chiar acum”
- obținerea unui server DHCP funcțional cu posibilitatea de a selecta adresa IP a utilizatorului pe baza adresei MAC a utilizatorului sau a combinației MAC switch+port (Opțiunea 82)
- scrierea unei alte biciclete (Oh! aceasta este activitatea mea preferată)
- obținerea de feedback despre nepriceperea mea pe HabrHabr (sau mai bine un invitație) 😉
Rezultatul: funcționează 😉 Testat pe sistemele de operare FreeBSD și Ubuntu. Teoretic, codul poate fi cerut să funcționeze pe orice sistem de operare, deoarece nu există legături specifice în cod.
Atenție! Mai departe sunt multe.
Linkul către depozitul pentru cei care .
Procesul de instalare, configurare și utilizare a rezultatului „studiului materiei” este mai jos, iar apoi un pic de teorie despre protocolul DHCP. Pentru mine. Și pentru istorie 😉
Un pic de teorie
Ce este DHCP
Este un protocol de rețea care permite unui dispozitiv să își afle adresa IP (și alte parametere precum gateway, DNS și altele) de la serverul DHCP. Schimbul de pachete se face prin protocolul UDP. Principiul general de funcționare al dispozitivului la cererea parametrilor rețelei este următorul:
- Dispozitivul (clientul) trimite o solicitare UDP de tip broadcast (DHCPDISCOVER) în toată rețeaua cu cererea „cineva, dă-mi o adresă IP”. În general (dar nu întotdeauna), cererea se face de pe portul 68 (origine), iar destinația — portul 67 (destinație). Unele dispozitive trimit pachete și de pe portul 67. În interiorul pachetului DHCPDISCOVER este inclusă adresa MAC a clientului.
- Toate serverele DHCP din rețea (și pot fi mai multe) formează pentru dispozitivul care a trimis DHCPDISCOVER, o ofertă DHCPOFFER cu setările de rețea și, de asemenea, o trimite în modul broadcast în rețea. Identificarea persoanei căreia îi este destinat acest pachet se face pe baza adresei MAC a clientului, furnizată anterior în cererea DHCPDISCOVER.
- Clientul primește pachete cu oferte de configurare a rețelei, alege pe cel mai atrăgător (criteriile pot varia, de exemplu, și în funcție de timpul de livrare a pachetului, numărul de rute intermediare) și face o „cerere oficială” DHCPREQUEST cu configurațiile de rețea de la serverul DHCP preferat. În acest caz, pachetul este trimis deja către un server DHCP specific.
- Serverul care primește DHCPREQUEST trimite un pachet de format DHCPACK, în care reiterează configurațiile de rețea destinate acestui client.

În plus, există pachete DHCPINFORM care circulă de la client și scopul cărora este să informeze serverul DHCP că „clientul este activ” și utilizează configurațiile de rețea emise. În implementarea acestui server, aceste pachete sunt ignorate.
Formatul pachetelor
În general, cadrul pachetului Ethernet arată cam așa:
![]()
În cazul nostru, vom analiza doar datele conținutului pachetului UDP, fără capetele protocoalelor de nivel OSI, și anume structura DHCP:
DHCPDISCOVER
Astfel, procesul de obținere a adresei IP pentru un dispozitiv începe cu clientul DHCP care trimite o solicitare de broadcast de la portul 68 la 255.255.255.255:67. În acest pachet, clientul include adresa sa MAC, precum și ceea ce dorește să obțină de la serverul DHCP. Structura pachetului este descrisă sub formă de tabel mai jos.
Tabelul structurii pachetului DHCPDISCOVER
Poziția în pachet
Numele valorii
Exemplu
Reprezentare
Byte
Explicație
1
Boot Request
1
Hex
1
Tipul mesajului. 1 — solicitare de la client la server, 2 — răspuns de la server la client
2
Tip hardware
1
Hex
1
Tipul adresei hardware, în acest protocol 1 — MAC
3
Lungimea adresei hardware
6
Hex
1
Lungimea adresei MAC a dispozitivului
4
Sărituri
1
Hex
1
Numărul de rute intermediare
5
ID-ul tranzacției
23:cf:de:1d
Hex
4
Identificatorul unic al tranzacției. Este generat de client la începutul operației de solicitare
7
Timpul scurs
0
Hex
4
Timpul în secunde de la începutul procesului de obținere a adresei
9
Flayere Bootp
0
Hex
2
Niște steaguri care pot fi setate ca indicații ale parametrilor protocolului
11
Adresa IP a clientului
0.0.0.0
Șirul
4
Adresa IP a clientului (dacă există)
15
Adresa IP a clientului
0.0.0.0
Șirul
4
Adresa IP propusă de server (dacă există)
19
Adresa IP a serverului următor
0.0.0.0
Șirul
4
Adresa IP a serverului (dacă se cunoaște)
23
Adresa IP a agentului de retransmisie
172.16.114.41
Șirul
4
Adresa IP a agentului de retransmisie (de exemplu, switch)
27
Adresa MAC a clientului
14:d6:4d:a7:c9:55
Hex
6
Adresa MAC a expeditorului pachetului (client)
31
Umplutura adresei hardware a clientului
Hex
10
Spațiu rezervat. De obicei, umplut cu zerouri
41
Numele gazdei serverului
Șirul
64
Numele serverului DHCP. De obicei, nu este transmis
105
Numele fișierului de boot
Șirul
128
Numele fișierului de pe server utilizat de stațiile fără disc la încărcare
235
Magic cookie
63:82:53:63
Hex
4
Un număr „magical” care poate fi folosit pentru a determina că acest pachet aparține protocolului DHCP
Opțiuni DHCP. Pot fi în orice ordine
236
Numărul opțiunii
53
Dec
1
Opțiunea 53, care definește tipul de pachet DHCP
1 — DHCPDISCOVER
3 — DHCPREQUEST
2 — DHCPOFFER
5 — DHCPACK
8 — DHCPINFORM
Lungimea opțiunii
1
Dec
1
Valoarea opțiunii
1
Dec
1
Numărul opțiunii
50
Dec
1
Ce adresă IP dorește să obțină clientul
Lungimea opțiunii
4
Dec
1
Valoarea opțiunii
172.16.134.61
Șirul
4
Numărul opțiunii
55
1
Parametrii de rețea solicitați de client. Compoziția poate varia
01 — Masca de rețea
03 — Gateway
06 — DNS
oc — Numele gazdelor
0f — numele domeniului de rețea
1c — adresa de difuzare (broadcast)
42 — numele serverului TFTP
79 — Ruta statică fără clasă
Lungimea opțiunii
8
1
Valoarea opțiunii
01:03:06:0c:0f:1c:42:79
8
Numărul opțiunii
82
Dec
Opțiunea 82, care transmite adresa MAC a dispozitivului - retransmițător și unele valori suplimentare.
Cel mai adesea - portul switch-ului pe care funcționează clientul final DHCP. În această opțiune sunt „înglobate” parametrii suplimentari. Primul octet - numărul „subopțiunii”, al doilea este lungimea, iar mai departe este valoarea acesteia.
În acest caz, în opțiunea 82, sunt incluse subopțiunile:
Agent Circuit ID = 00:04:00:01:00:04, unde ultimele două octete sunt portul clientului DHCP de la care a venit cererea
Agent Remote ID = 00:06:c8:be:19:93:11:48 — adresa MAC a dispozitivului retransmițător DHCP
Lungimea opțiunii
18
Dec
Valoarea opțiunii
01:06
00:04:00:01:00:04
02:08
00:06:c8:be:19:93:11:48
Hex
Sfârșitul pachetului
255
Dec
1
255 simbolizează sfârșitul pachetului
DHCPOFFER
Odată ce serverul primește pachetul DHCPDISCOVER și vede că poate oferi ceva clientului din solicitare, el formează un răspuns — DHCPDISCOVER. Răspunsul este trimis pe portul „de unde a venit”, prin difuzare, deoarece, în acel moment, clientul nu are încă adresă IP, prin urmare pachetul poate fi acceptat doar dacă este trimis prin difuzare. Clientul recunoaște că acesta este un pachet pentru el prin adresa sa MAC din interiorul pachetului, precum și prin numărul de tranzacție generat în momentul creării primului pachet.
Tabelul structurii pachetului DHCPOFFER
Poziția în pachet
Denumirea valorii (acceptată în general)
Exemplu
Reprezentare
Byte
Explicație
1
Boot Request
1
Hex
1
Tipul mesajului. 1 — solicitare de la client la server, 2 — răspuns de la server la client
2
Tip hardware
1
Hex
1
Tipul adresei hardware, în acest protocol 1 — MAC
3
Lungimea adresei hardware
6
Hex
1
Lungimea adresei MAC a dispozitivului
4
Sărituri
1
Hex
1
Numărul de rute intermediare
5
ID-ul tranzacției
23:cf:de:1d
Hex
4
Identificatorul unic al tranzacției. Este generat de client la începutul operației de solicitare
7
Timpul scurs
0
Hex
4
Timpul în secunde de la începutul procesului de obținere a adresei
9
Flayere Bootp
0
Hex
2
Unele steaguri care pot fi setate ca indicații pentru parametrii protocolului. În acest caz, 0 — înseamnă tipul de solicitare Unicast
11
Adresa IP a clientului
0.0.0.0
Șirul
4
Adresa IP a clientului (dacă există)
15
Adresa IP a clientului
172.16.134.61
Șirul
4
Adresa IP propusă de server (dacă există)
19
Adresa IP a serverului următor
0.0.0.0
Șirul
4
Adresa IP a serverului (dacă se cunoaște)
23
Adresa IP a agentului de retransmisie
172.16.114.41
Șirul
4
Adresa IP a agentului de retransmisie (de exemplu, switch)
27
Adresa MAC a clientului
14:d6:4d:a7:c9:55
Hex
6
Adresa MAC a expeditorului pachetului (client)
31
Umplutura adresei hardware a clientului
Hex
10
Spațiu rezervat. De obicei, umplut cu zerouri
41
Numele gazdei serverului
Șirul
64
Numele serverului DHCP. De obicei, nu este transmis
105
Numele fișierului de boot
Șirul
128
Numele fișierului de pe server utilizat de stațiile fără disc la încărcare
235
Magic cookie
63:82:53:63
Hex
4
Un număr „magical” care poate fi folosit pentru a determina că acest pachet aparține protocolului DHCP
Opțiuni DHCP. Pot fi în orice ordine
236
Numărul opțiunii
53
Dec
1
Opțiunea 53, care definește tipul de pachet DHCP 2 — DHCPOFFER
Lungimea opțiunii
1
Dec
1
Valoarea opțiunii
2
Dec
1
Numărul opțiunii
1
Dec
1
Opțiunea care oferă clientului DHCP masca de rețea
Lungimea opțiunii
4
Dec
1
Valoarea opțiunii
255.255.224.0
Șirul
4
Numărul opțiunii
3
Dec
1
Opțiunea care oferă clientului DHCP gateway-ul implicit
Lungimea opțiunii
4
Dec
1
Valoarea opțiunii
172.16.12.1
Șirul
4
Numărul opțiunii
6
Dec
1
Opțiunea care oferă clientului DHCP DNS
Lungimea opțiunii
4
Dec
1
Valoarea opțiunii
8.8.8.8
Șirul
4
Numărul opțiunii
51
Dec
1
Timpul de viață al parametrilor de rețea emisi, în secunde, după care clientul DHCP trebuie să le solicite din nou
Lungimea opțiunii
4
Dec
1
Valoarea opțiunii
86400
Dec
4
Numărul opțiunii
82
Dec
1
Opțiunea 82, repetă ceea ce a venit în DHCPDISCOVER
Lungimea opțiunii
18
Dec
1
Valoarea opțiunii
01:08:00:06:00
01:01:00:00:01
02:06:00:03:0f
26:4d:ec
Dec
18
Sfârșitul pachetului
255
Dec
1
255 simbolizează sfârșitul pachetului
DHCPREQUEST
După ce clientul primește DHCPOFFER, acesta formează un pachet cu o solicitare de parametrii de rețea nu către toate serverele DHCP din rețea, ci doar către unul specific, al cărui DHCPOFFER i-a plăcut cel mai mult. Criteriile de apreciere pot fi diverse și depind de implementarea clientului DHCP. Destinatarul solicitării este specificat prin adresa MAC a serverului DHCP. De asemenea, pachetul DHCPREQUEST poate fi trimis de client fără a forma anterior un DHCPDISCOVER, dacă adresa IP de la server a fost obținută anterior.
Tabelul structurii pachetului DHCPREQUEST
Poziția în pachet
Denumirea valorii (acceptată în general)
Exemplu
Reprezentare
Byte
Explicație
1
Boot Request
1
Hex
1
Tipul mesajului. 1 — solicitare de la client la server, 2 — răspuns de la server la client
2
Tip hardware
1
Hex
1
Tipul adresei hardware, în acest protocol 1 — MAC
3
Lungimea adresei hardware
6
Hex
1
Lungimea adresei MAC a dispozitivului
4
Sărituri
1
Hex
1
Numărul de rute intermediare
5
ID-ul tranzacției
23:cf:de:1d
Hex
4
Identificatorul unic al tranzacției. Este generat de client la începutul operației de solicitare
7
Timpul scurs
0
Hex
4
Timpul în secunde de la începutul procesului de obținere a adresei
9
Flayere Bootp
8000
Hex
2
Anumiți indicatori care pot fi setați ca indicație a parametrilor protocolului. În acest caz, este setat „broadcast”
11
Adresa IP a clientului
0.0.0.0
Șirul
4
Adresa IP a clientului (dacă există)
15
Adresa IP a clientului
172.16.134.61
Șirul
4
Adresa IP propusă de server (dacă există)
19
Adresa IP a serverului următor
0.0.0.0
Șirul
4
Adresa IP a serverului (dacă se cunoaște)
23
Adresa IP a agentului de retransmisie
172.16.114.41
Șirul
4
Adresa IP a agentului de retransmisie (de exemplu, switch)
27
Adresa MAC a clientului
14:d6:4d:a7:c9:55
Hex
6
Adresa MAC a expeditorului pachetului (client)
31
Umplutura adresei hardware a clientului
Hex
10
Spațiu rezervat. De obicei, umplut cu zerouri
41
Numele gazdei serverului
Șirul
64
Numele serverului DHCP. De obicei, nu este transmis
105
Numele fișierului de boot
Șirul
128
Numele fișierului de pe server utilizat de stațiile fără disc la încărcare
235
Magic cookie
63:82:53:63
Hex
4
Un număr „magical” care poate fi folosit pentru a determina că acest pachet aparține protocolului DHCP
Opțiuni DHCP. Pot fi în orice ordine
236
Numărul opțiunii
53
Dec
3
Opțiunea 53, care definește tipul pachetului DHCP 3 — DHCPREQUEST
Lungimea opțiunii
1
Dec
1
Valoarea opțiunii
3
Dec
1
Numărul opțiunii
61
Dec
1
Identificatorul clientului: 01 (pentru Ethernet) + adresa MAC a clientului
Lungimea opțiunii
7
Dec
1
Valoarea opțiunii
01:2c:ab:25:ff:72:a6
Hex
7
Numărul opțiunii
60
Dec
Identificatorul clasei vendorului. În cazul meu, comunică versiunea clientului DHCP. Este posibil ca alte dispozitive să returneze altceva. De exemplu, Windows raportează MSFT 5.0
Lungimea opțiunii
11
Dec
Valoarea opțiunii
udhcp 0.9.8
Șirul
Numărul opțiunii
55
1
Parametrii de rețea solicitați de client. Compoziția poate varia
01 — Masca de rețea
03 — Gateway
06 — DNS
oc — Numele gazdelor
0f — numele domeniului de rețea
1c — adresa de difuzare (broadcast)
42 — numele serverului TFTP
79 — Ruta statică fără clasă
Lungimea opțiunii
8
1
Valoarea opțiunii
01:03:06:0c:0f:1c:42:79
8
Numărul opțiunii
82
Dec
1
Opțiunea 82, repetă ceea ce a venit în DHCPDISCOVER
Lungimea opțiunii
18
Dec
1
Valoarea opțiunii
01:08:00:06:00
01:01:00:00:01
02:06:00:03:0f
26:4d:ec
Dec
18
Sfârșitul pachetului
255
Dec
1
255 simbolizează sfârșitul pachetului
DHCPACK
Ca o confirmare că „da, acesta este IP-ul tău, și nu-l voi da altcuiva”, serverul DHCP transmite un pachet în format DHCPACK către client. Acesta, la fel ca celelalte pachete, este trimis în mod difuz. Totuși, în codul de mai jos al serverului DHCP implementat pe Python, pentru siguranță, duplic pachetul de solicitare difuzând-l pe un IP specific al clientului, dacă acesta este deja cunoscut. Serverul DHCP nu îi pasă dacă pachetul DHCPACK a ajuns la client. Dacă clientul nu primește DHCPACK, acesta repetă pur și simplu DHCPREQUEST după un timp.
Tabelul structurii pachetului DHCPACK
Poziția în pachet
Denumirea valorii (acceptată în general)
Exemplu
Reprezentare
Byte
Explicație
1
Boot Request
2
Hex
1
Tipul mesajului. 1 — solicitare de la client la server, 2 — răspuns de la server la client
2
Tip hardware
1
Hex
1
Tipul adresei hardware, în acest protocol 1 — MAC
3
Lungimea adresei hardware
6
Hex
1
Lungimea adresei MAC a dispozitivului
4
Sărituri
1
Hex
1
Numărul de rute intermediare
5
ID-ul tranzacției
23:cf:de:1d
Hex
4
Identificatorul unic al tranzacției. Este generat de client la începutul operației de solicitare
7
Timpul scurs
0
Hex
4
Timpul în secunde de la începutul procesului de obținere a adresei
9
Flayere Bootp
8000
Hex
2
Anumiți indicatori care pot fi setați ca indicație a parametrilor protocolului. În acest caz, este setat „broadcast”
11
Adresa IP a clientului
0.0.0.0
Șirul
4
Adresa IP a clientului (dacă există)
15
Adresa IP a clientului
172.16.134.61
Șirul
4
Adresa IP propusă de server (dacă există)
19
Adresa IP a serverului următor
0.0.0.0
Șirul
4
Adresa IP a serverului (dacă se cunoaște)
23
Adresa IP a agentului de retransmisie
172.16.114.41
Șirul
4
Adresa IP a agentului de retransmisie (de exemplu, switch)
27
Adresa MAC a clientului
14:d6:4d:a7:c9:55
Hex
6
Adresa MAC a expeditorului pachetului (client)
31
Umplutura adresei hardware a clientului
Hex
10
Spațiu rezervat. De obicei, umplut cu zerouri
41
Numele gazdei serverului
Șirul
64
Numele serverului DHCP. De obicei, nu este transmis
105
Numele fișierului de boot
Șirul
128
Numele fișierului de pe server utilizat de stațiile fără disc la încărcare
235
Magic cookie
63:82:53:63
Hex
4
Un număr „magical” care poate fi folosit pentru a determina că acest pachet aparține protocolului DHCP
Opțiuni DHCP. Pot fi în orice ordine
236
Numărul opțiunii
53
Dec
3
Opțiunea 53, care definește tipul pachetului DHCP 5 — DHCPACK
Lungimea opțiunii
1
Dec
1
Valoarea opțiunii
5
Dec
1
Numărul opțiunii
1
Dec
1
Opțiunea care oferă clientului DHCP masca de rețea
Lungimea opțiunii
4
Dec
1
Valoarea opțiunii
255.255.224.0
Șirul
4
Numărul opțiunii
3
Dec
1
Opțiunea care oferă clientului DHCP gateway-ul implicit
Lungimea opțiunii
4
Dec
1
Valoarea opțiunii
172.16.12.1
Șirul
4
Numărul opțiunii
6
Dec
1
Opțiunea care oferă clientului DHCP DNS
Lungimea opțiunii
4
Dec
1
Valoarea opțiunii
8.8.8.8
Șirul
4
Numărul opțiunii
51
Dec
1
Timpul de viață al parametrilor de rețea emisi, în secunde, după care clientul DHCP trebuie să le solicite din nou
Lungimea opțiunii
4
Dec
1
Valoarea opțiunii
86400
Dec
4
Numărul opțiunii
82
Dec
1
Opțiunea 82, repetă ceea ce a venit în DHCPDISCOVER
Lungimea opțiunii
18
Dec
1
Valoarea opțiunii
01:08:00:06:00
01:01:00:00:01
02:06:00:03:0f
26:4d:ec
Dec
18
Sfârșitul pachetului
255
Dec
1
255 simbolizează sfârșitul pachetului
Instalare
Instalarea constă efectiv în instalarea modulelor Python necesare pentru funcționare. Se presupune că MySQL este deja instalat și configurat.
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
Creăm baza de date MySQL, încărcăm dump-ul pydhcp.sql și configurăm fișierul de configurare.
Configurație
Toate setările serverului sunt stocate într-un fișier în format XML. Fișierul de referință:
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')Acum, să discutăm mai în detaliu despre etichete:
Secțiunea dhcpserver descrie setările principale pentru pornirea serverului, și anume:
- host — ce adresă IP ascultă serverul pe portul 67
- broadcast — ce IP este adresa de broadcast pentru DHCPOFFER și DHCPACK
- DHCPServer — ce IP are serverul DHCP
- LeaseTime — timpul de închiriere a adresei IP emise
- ThreadLimit — câte fire pot fi rulate simultan pentru a procesa pachetele UDP primite pe portul 67. Se presupune că ajută în proiectele cu încărcare mare 😉
- defaultMask, defaultRouter, defaultDNS — acestea sunt valorile oferite utilizatorului în mod implicit, dacă IP-ul se găsește în baza de date, dar nu sunt specificate parametrii suplimentari pentru acesta
Secțiunea mysql:
host, username, password, basename — totul se explică de la sine. Structura aproximativă a bazei de date este disponibilă la
Secțiunea query: aici sunt descrise interogările pentru obținerea OFFER/ACK:
- offer_count — numărul de rânduri cu interogări care returnează rezultate de tipul ip, mask, router, dns
- offer_n — rândul interogării. Dacă răspunsul este gol, se execută următoarea interogare offer
- history_sql — interogare care scrie, de exemplu, în «istoricul autorizării» pentru utilizator
În interogări pot participa orice variabile din secțiunea options sau opțiuni din protocolul DHCP.
Secțiunea options. Aici devine mai interesant. Aici putem crea variabile pe care le putem folosi ulterior în secțiunea query.
De exemplu:
option_82_hex:sw_port1:20:22, acest cod ia întreaga linie primită în cererea DHCP opțiunea 82, în format hex, în intervalul de la 20 la 22 de octete inclusiv și o stochează într-o nouă variabilă sw_port1 (portul switch-ului de unde a venit cererea)
opțiune_82_hex:sw_mac:26:40, definim variabila sw_mac, luând hex din intervalul 26:40
Pentru a vedea toate opțiunile posibile care pot fi utilizate în cereri, se poate face prin lansarea serverului cu cheia -d. Vom observa un log asemănător:
--a sosit pachetul DHCPINFORM pe portul 67, de la 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'}Astfel, putem învârti orice variabilă în {} și aceasta va fi utilizată în cererea SQL.
Să ne amintim pentru istorie, ce adresă IP a primit clientul:


Pornirea serverului
.\/pydhcpdb.py -d -c config.xml
— d modul de ieșire în consolă DEBUG
— c <numele_fisierului> fișier de configurare
Analiză post-acțiune
Și acum, mai în detaliu despre implementarea serverului în Python. Este o provocare. Python a fost studiat „pe parcurs”. Multe aspecte au fost realizate în stilul: „wow, am făcut ceva ce funcționează”. Nu sunt deloc optimizate și au rămas în această formă în principal din cauza experienței limitate în programarea în Python. Mă voi concentra pe cele mai interesante aspecte ale implementării serverului în „cod”.
Parser pentru fișierul de configurare XML
Se folosește modulul standard Python xml.dom. Pare simplu, dar în timpul implementării am simțit cu adevărat lipsa unei documentații utile și a unor exemple în rețea cu utilizarea acestui modul.
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
Surprinzător, multithreading-ul în Python este implementat foarte clar și simplu.
def PacketWork(data,addr):
...
# implementarea analizei pachetului primit și a răspunsului acestuia
...
while True:
data, addr = udp_socket.recvfrom(1024) # așteptăm un pachet UDP
thread = threading.Thread(target=PacketWork, args=(data,addr,)).start() # pe măsură ce ajunge, lansăm în fundal funcția PacketWork definită anterior cu parametrii
while threading.active_count() > gconfig["dhcp_ThreadLimit"]:
time.sleep(1) # dacă numărul de fire de execuție deja pornite este mai mare decât în setări, așteptăm până scadePrimirea / trimiterea pachetului DHCP
Pentru a intercepta pachetele UDP care trec prin placa de rețea, trebuie să „ridici” un socket:
udp_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,socket.IPPROTO_UDP) udp_socket.bind((gconfig["dhcp_host"],67))
, unde flag-urile:
- AF_INET — înseamnă că formatul adresei va fi IP: port. Poate fi și AF_UNIX — unde adresa este specificată printr-un nume de fișier.
- SOCK_DGRAM — înseamnă că primim nu un „pachet brut”, ci deja procesat prin firewall, și cu pachetul tăiat parțial. Adică primim doar pachetul UDP fără componenta „fizică” a ambalajului pachetului UDP. Dacă folosești flag-ul SOCK_RAW, va trebui să parsezi și acel „ambalaj”.
Transmisia unui pachet poate fi fie prin broadcast:
udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) # schimbați socketul în modul de transmitere broadcast
rz=udp_socket.sendto(packetack, (gconfig["broadcast"],68)), fie către adresa „de unde a venit pachetul”:
udp_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # schimbați socketul în modul "multi-audiență"
rz=udp_socket.sendto(packetack, addr), unde SOL_SOCKET înseamnă „nivelul protocolului” pentru setarea opțiunilor,
, SO_BROADCAST este opțiunea care indică faptul că pachetul este trimis „prin broadcast”
, SO_REUSEADDR este opțiunea care schimbă socketul în modul „multi-audiență”. Teoretic, aceasta nu este necesară în acest caz, dar pe unul dintre serverele FreeBSD, pe care l-am testat, fără această opțiune codul nu a funcționat.
Analiza pachetului DHCP
Aici mi-a plăcut cu adevărat Python. Se pare că din „cutie” permite lucrul destul de liber cu codul byte. Permițându-i să fie tradus foarte simplu în valori zecimale, șiruri și hex – adică exact ceea ce avem nevoie pentru a înțelege structura pachetului. De exemplu, se poate obține un interval de bytes în HEX și bytes simpli:
res["xidhex"]=data[4:8].hex()
res["xidbyte"]=data[4:8], împachetând bytes într-o structură:
res["flags"]=pack('BB',data[10],data[11])Obținerea IP-ului din structură:
res["ciaddr"]=socket.inet_ntoa(pack('BBBB',data[12],data[13],data[14],data[15]));Și invers:
res=res+socket.inet_pton(socket.AF_INET, gconfig["dhcp_Server"])
Cam atât 😉
Sursa: habr.com
