
L'obiettivo di questo progetto era:
- Studio del protocollo DHCP in funzionamento su rete IPv4
- Studio di Python (un po' più di zero 😉 )
- sostituzione del server (il mio fork), originale , che diventa sempre più difficile raccogliere per il nuovo sistema operativo. E non mi piace che il binario non possa essere "cambiato subito"
- ottenere un server DHCP funzionante con la possibilità di ottenere l'indirizzo IP dell'abbonato per MAC dell'abbonato o per la combinazione MAC switch + porta (Opzione 82)
- scrivere un altro progetto (Oh! è la mia attività preferita)
- ottenere rimproveri per la mia goffaggine su HabrHabr (meglio un invito) 😉
Risultato: funziona 😉 Provato su sistemi operativi FreeBSD e Ubuntu. Teoricamente, il codice può essere fatto funzionare su qualsiasi sistema operativo, poiché non sembra avere legami specifici nel codice.
Attenzione! Avanti ci sarà molto.
Link al repository per i curiosi .
Il processo di installazione, configurazione e utilizzo del risultato dello "studio dei materiali" è molto più in basso, e di seguito un po' di teoria sul protocollo DHCP. Per me. E per la storia 😉
Un po' di teoria
Che cos'è DHCP
È un protocollo di rete che consente a un dispositivo di scoprire il proprio indirizzo IP (e altri parametri come gateway, DNS e altro) da un server DHCP. Lo scambio di pacchetti avviene tramite il protocollo UDP. Il principio generale di funzionamento del dispositivo durante la richiesta dei parametri di rete è il seguente:
- Il dispositivo (cliente) invia una richiesta broadcast UDP (DHCPDISCOVER) in tutta la rete con la richiesta "qualcuno, datemi un indirizzo IP". Di solito (ma non sempre) la richiesta avviene dalla porta 68 (sorgente), mentre la destinazione è la porta 67 (destinazione). Alcuni dispositivi inviano pacchetti anche dalla porta 67. All'interno del pacchetto DHCPDISCOVER è incluso l'indirizzo MAC del dispositivo cliente.
- Tutti i server DHCP presenti nella rete (e ce ne possono essere diversi) formano un'offerta DHCPOFFER con le impostazioni di rete per il dispositivo che ha inviato il DHCPDISCOVER e la inviano anche in broadcasting attraverso la rete. L'identificazione di a chi è destinato questo pacchetto avviene attraverso l'indirizzo MAC del cliente, fornito in precedenza nella richiesta DHCPDISCOVER.
- Il cliente riceve pacchetti con offerte di configurazione di rete, sceglie quello più attraente (i criteri possono essere vari, ad esempio anche in base ai tempi di consegna del pacchetto, al numero di percorsi intermedi) e invia al server DHCP scelto una "richiesta ufficiale" DHCPREQUEST con le impostazioni di rete. In questo caso, il pacchetto è indirizzato a un server DHCP specifico.
- Il server che riceve il DHCPREQUEST inoltra un pacchetto in formato DHCPACK, nel quale elenca nuovamente le impostazioni di rete destinate a questo cliente.

Inoltre, ci sono pacchetti DHCPINFORM che vengono inviati dal cliente, il cui scopo è informare il server DHCP che il "cliente è attivo" e sta utilizzando le impostazioni di rete fornite. Nel caso di questo server, questi pacchetti vengono ignorati.
Formato dei pacchetti
In generale, il frame del pacchetto Ethernet appare più o meno così:
![]()
In questo caso, considereremo solo i dati contenuti nel pacchetto UDP, senza gli header dei protocolli dei livelli OSI, precisamente la struttura DHCP:
DHCPDISCOVER
Quindi, il processo per ottenere un indirizzo IP per il dispositivo inizia con il cliente DHCP che invia una richiesta broadcast dalla porta 68 a 255.255.255.255:67. In questo pacchetto, il cliente include il proprio indirizzo MAC e specifica cosa desidera ricevere dal server DHCP. La struttura del pacchetto è descritta nella tabella qui sotto.
Tabella della struttura del pacchetto DHCPDISCOVER
Posizione nel pacchetto
Nome del valore
Esempio
Rappresentazione
Byte
Spiegazione
1
Richiesta di avvio
1
Esadecimale
1
Tipo di messaggio. 1 — richiesta dal cliente al server, 2 — risposta dal server al cliente
2
Tipo hardware
1
Esadecimale
1
Tipo di indirizzo hardware, in questo protocollo 1 — MAC
3
Lunghezza dell'indirizzo hardware
6
Esadecimale
1
Lunghezza dell'indirizzo MAC del dispositivo
4
Salti
1
Esadecimale
1
Numero di percorsi intermedi
5
ID transazione
23:cf:de:1d
Esadecimale
4
Identificatore unico della transazione. Generato dal cliente all'inizio dell'operazione di richiesta.
7
Secondi trascorsi
0
Esadecimale
4
Tempo in secondi dall'inizio del processo di acquisizione dell'indirizzo.
9
Flag Bootp
0
Esadecimale
2
Alcuni flag che possono essere impostati come indicazioni dei parametri del protocollo.
11
Indirizzo IP del cliente
0.0.0.0
Riga
4
Indirizzo IP del cliente (se presente)
15
Il tuo indirizzo IP del cliente
0.0.0.0
Riga
4
Indirizzo IP proposto dal server (se presente)
19
Indirizzo IP del server successivo
0.0.0.0
Riga
4
Indirizzo IP del server (se noto)
23
Indirizzo IP dell'agente di relay
172.16.114.41
Riga
4
Indirizzo IP dell'agente di relay (ad esempio uno switch).
27
Indirizzo MAC del cliente
14:d6:4d:a7:c9:55
Esadecimale
6
Indirizzo MAC del mittente del pacchetto (cliente).
31
Padding dell'indirizzo hardware del cliente
Esadecimale
10
Spazio riservato. Di solito riempito di zeri.
41
Nome host del server
Riga
64
Nome del server DHCP. Di solito non viene trasmesso.
105
Nome del file di avvio
Riga
128
Nome del file sul server, usato dalle stazioni senza disco durante il caricamento
235
Magic cookie
63:82:53:63
Esadecimale
4
Il numero «magico» attraverso il quale è possibile determinare, tra l'altro, che questo pacchetto appartiene al protocollo DHCP
Opzioni DHCP. Possono essere in qualsiasi ordine
236
Numero dell'opzione
53
Dec
1
Opzione 53, che definisce il tipo di pacchetto DHCP
1 — DHCPDISCOVER
3 — DHCPREQUEST
2 — DHCPOFFER
5 — DHCPACK
8 — DHCPINFORM
Lunghezza dell'opzione
1
Dec
1
Valore dell'opzione
1
Dec
1
Numero dell'opzione
50
Dec
1
Quale indirizzo IP desidera ricevere il client
Lunghezza dell'opzione
4
Dec
1
Valore dell'opzione
172.16.134.61
Riga
4
Numero dell'opzione
55
1
Parametri di rete richiesti dal client. La composizione può variare
01 — Maschera di rete
03 — Gateway
06 — DNS
oc — Nome host
0f — Nome di dominio di rete
1c — Indirizzo di richiesta di trasmissione (broadcast)
42 — Nome del server TFTP
79 — Rotta Statica Senza Classe
Lunghezza dell'opzione
8
1
Valore dell'opzione
01:03:06:0c:0f:1c:42:79
8
Numero dell'opzione
82
Dec
Opzione 82, che trasmette l'indirizzo MAC del dispositivo — il ripetitore e alcuni valori aggiuntivi.
Di solito — il porto dello switch su cui opera il client finale DHCP. In questa opzione sono «incapsulati» parametri aggiuntivi. Il primo byte è il numero della «sottopzione», il secondo è la sua lunghezza, seguito dal suo valore.
In questo caso, nella opzione 82, sono incapsulate le sottopzioni:
Agent Circuit ID = 00:04:00:01:00:04, dove gli ultimi due byte rappresentano il porto del client DHCP da cui è arrivata la richiesta
Agent Remote ID = 00:06:c8:be:19:93:11:48 — indirizzo MAC del dispositivo ripetitore DHCP
Lunghezza dell'opzione
18
Dec
Valore dell'opzione
01:06
00:04:00:01:00:04
02:08
00:06:c8:be:19:93:11:48
Esadecimale
Fine del pacchetto
255
Dec
1
255 simboleggia la fine del pacchetto
DHCPOFFER
Non appena il server riceve il pacchetto DHCPDISCOVER e vede che può offrire qualcosa al client richiesto, formula una risposta — DHCPDISCOVER. La risposta viene inviata al porto «da cui è arrivata», in broadcast, poiché in quel momento il client non ha ancora un indirizzo IP, quindi il pacchetto può essere ricevuto solo se inviato in broadcast. Il client riconosce che si tratta di un pacchetto per lui grazie al suo indirizzo MAC all'interno del pacchetto, così come al numero di transazione che genera al momento della creazione del primo pacchetto.
Tabella della struttura del pacchetto DHCPOFFER
Posizione nel pacchetto
Nome del valore (comune)
Esempio
Rappresentazione
Byte
Spiegazione
1
Richiesta di avvio
1
Esadecimale
1
Tipo di messaggio. 1 — richiesta dal cliente al server, 2 — risposta dal server al cliente
2
Tipo hardware
1
Esadecimale
1
Tipo di indirizzo hardware, in questo protocollo 1 — MAC
3
Lunghezza dell'indirizzo hardware
6
Esadecimale
1
Lunghezza dell'indirizzo MAC del dispositivo
4
Salti
1
Esadecimale
1
Numero di percorsi intermedi
5
ID transazione
23:cf:de:1d
Esadecimale
4
Identificatore unico della transazione. Generato dal cliente all'inizio dell'operazione di richiesta.
7
Secondi trascorsi
0
Esadecimale
4
Tempo in secondi dall'inizio del processo di acquisizione dell'indirizzo.
9
Flag Bootp
0
Esadecimale
2
Alcuni flag che possono essere impostati come indicazione dei parametri del protocollo. In questo caso, 0 — indica il tipo di richiesta Unicast
11
Indirizzo IP del cliente
0.0.0.0
Riga
4
Indirizzo IP del cliente (se presente)
15
Il tuo indirizzo IP del cliente
172.16.134.61
Riga
4
Indirizzo IP proposto dal server (se presente)
19
Indirizzo IP del server successivo
0.0.0.0
Riga
4
Indirizzo IP del server (se noto)
23
Indirizzo IP dell'agente di relay
172.16.114.41
Riga
4
Indirizzo IP dell'agente di relay (ad esempio uno switch).
27
Indirizzo MAC del cliente
14:d6:4d:a7:c9:55
Esadecimale
6
Indirizzo MAC del mittente del pacchetto (cliente).
31
Padding dell'indirizzo hardware del cliente
Esadecimale
10
Spazio riservato. Di solito riempito di zeri.
41
Nome host del server
Riga
64
Nome del server DHCP. Di solito non viene trasmesso.
105
Nome del file di avvio
Riga
128
Nome del file sul server, usato dalle stazioni senza disco durante il caricamento
235
Magic cookie
63:82:53:63
Esadecimale
4
Il numero «magico» attraverso il quale è possibile determinare, tra l'altro, che questo pacchetto appartiene al protocollo DHCP
Opzioni DHCP. Possono essere in qualsiasi ordine
236
Numero dell'opzione
53
Dec
1
Opzione 53, che definisce il tipo di pacchetto DHCP 2 — DHCPOFFER
Lunghezza dell'opzione
1
Dec
1
Valore dell'opzione
2
Dec
1
Numero dell'opzione
1
Dec
1
Opzione che offre al client DHCP la maschera di rete
Lunghezza dell'opzione
4
Dec
1
Valore dell'opzione
255.255.224.0
Riga
4
Numero dell'opzione
3
Dec
1
Opzione che offre al client DHCP il gateway predefinito
Lunghezza dell'opzione
4
Dec
1
Valore dell'opzione
172.16.12.1
Riga
4
Numero dell'opzione
6
Dec
1
Opzione che offre al client DHCP il DNS
Lunghezza dell'opzione
4
Dec
1
Valore dell'opzione
8.8.8.8
Riga
4
Numero dell'opzione
51
Dec
1
Tempo di vita dei parametri di rete assegnati in secondi, dopo il quale il client DHCP deve richiederli di nuovo
Lunghezza dell'opzione
4
Dec
1
Valore dell'opzione
86400
Dec
4
Numero dell'opzione
82
Dec
1
Opzione 82, ripete ciò che è stato ricevuto nel DHCPDISCOVER
Lunghezza dell'opzione
18
Dec
1
Valore dell'opzione
01:08:00:06:00
01:01:00:00:01
02:06:00:03:0f
26:4d:ec
Dec
18
Fine del pacchetto
255
Dec
1
255 simboleggia la fine del pacchetto
DHCPREQUEST
Dopo che il client riceve il DHCPOFFER, genera un pacchetto con una richiesta di parametri di rete non a tutti i server DHCP nella rete, ma solo a uno specifico, il cui DHCPOFFER gli è piaciuto di più. I criteri di "piaciuto" possono essere vari e dipendono dall'implementazione del client DHCP. Il destinatario della richiesta è specificato mediante l'indirizzo MAC del server DHCP. Inoltre, il pacchetto DHCPREQUEST può essere inviato dal client anche senza aver prima generato il DHCPDISCOVER, se l'indirizzo IP del server era già stato ottenuto in precedenza.
Tabella della struttura del pacchetto DHCPREQUEST
Posizione nel pacchetto
Nome del valore (comune)
Esempio
Rappresentazione
Byte
Spiegazione
1
Richiesta di avvio
1
Esadecimale
1
Tipo di messaggio. 1 — richiesta dal cliente al server, 2 — risposta dal server al cliente
2
Tipo hardware
1
Esadecimale
1
Tipo di indirizzo hardware, in questo protocollo 1 — MAC
3
Lunghezza dell'indirizzo hardware
6
Esadecimale
1
Lunghezza dell'indirizzo MAC del dispositivo
4
Salti
1
Esadecimale
1
Numero di percorsi intermedi
5
ID transazione
23:cf:de:1d
Esadecimale
4
Identificatore unico della transazione. Generato dal cliente all'inizio dell'operazione di richiesta.
7
Secondi trascorsi
0
Esadecimale
4
Tempo in secondi dall'inizio del processo di acquisizione dell'indirizzo.
9
Flag Bootp
8000
Esadecimale
2
Alcuni flag che possono essere impostati come indicazione dei parametri del protocollo. In questo caso è stato impostato "broadcast"
11
Indirizzo IP del cliente
0.0.0.0
Riga
4
Indirizzo IP del cliente (se presente)
15
Il tuo indirizzo IP del cliente
172.16.134.61
Riga
4
Indirizzo IP proposto dal server (se presente)
19
Indirizzo IP del server successivo
0.0.0.0
Riga
4
Indirizzo IP del server (se noto)
23
Indirizzo IP dell'agente di relay
172.16.114.41
Riga
4
Indirizzo IP dell'agente di relay (ad esempio uno switch).
27
Indirizzo MAC del cliente
14:d6:4d:a7:c9:55
Esadecimale
6
Indirizzo MAC del mittente del pacchetto (cliente).
31
Padding dell'indirizzo hardware del cliente
Esadecimale
10
Spazio riservato. Di solito riempito di zeri.
41
Nome host del server
Riga
64
Nome del server DHCP. Di solito non viene trasmesso.
105
Nome del file di avvio
Riga
128
Nome del file sul server, usato dalle stazioni senza disco durante il caricamento
235
Magic cookie
63:82:53:63
Esadecimale
4
Il numero «magico» attraverso il quale è possibile determinare, tra l'altro, che questo pacchetto appartiene al protocollo DHCP
Opzioni DHCP. Possono essere in qualsiasi ordine
236
Numero dell'opzione
53
Dec
3
Opzione 53, che definisce il tipo di pacchetto DHCP 3 — DHCPREQUEST
Lunghezza dell'opzione
1
Dec
1
Valore dell'opzione
3
Dec
1
Numero dell'opzione
61
Dec
1
Identificatore del client: 01 (per Ethernet) + indirizzo MAC del client
Lunghezza dell'opzione
7
Dec
1
Valore dell'opzione
01:2c:ab:25:ff:72:a6
Esadecimale
7
Numero dell'opzione
60
Dec
"Vendor class identifier". Nel mio caso comunica la versione del client DHCP. È possibile che altri dispositivi restituiscano qualcosa di diverso. Windows, ad esempio, riporta MSFT 5.0
Lunghezza dell'opzione
11
Dec
Valore dell'opzione
udhcp 0.9.8
Riga
Numero dell'opzione
55
1
Parametri di rete richiesti dal client. La composizione può variare
01 — Maschera di rete
03 — Gateway
06 — DNS
oc — Nome host
0f — Nome di dominio di rete
1c — Indirizzo di richiesta di trasmissione (broadcast)
42 — Nome del server TFTP
79 — Rotta Statica Senza Classe
Lunghezza dell'opzione
8
1
Valore dell'opzione
01:03:06:0c:0f:1c:42:79
8
Numero dell'opzione
82
Dec
1
Opzione 82, ripete ciò che è stato ricevuto nel DHCPDISCOVER
Lunghezza dell'opzione
18
Dec
1
Valore dell'opzione
01:08:00:06:00
01:01:00:00:01
02:06:00:03:0f
26:4d:ec
Dec
18
Fine del pacchetto
255
Dec
1
255 simboleggia la fine del pacchetto
DHCPACK
Come conferma che "sì, esattamente, questo è il tuo indirizzo IP e non lo darò a nessun altro" da parte del server DHCP, viene inviata un pacchetto in formato DHCPACK dal server al client. Anche questo, come gli altri pacchetti, viene inviato in broadcast. Tuttavia, nel codice sottostante del server DHCP implementato in Python, per sicurezza duplico qualsiasi richiesta in broadcast, inviando un pacchetto a un IP specifico del client, se già noto. Inoltre, il server DHCP non è affatto preoccupato se il pacchetto DHCPACK è arrivato al client. Se il client non riceve il DHCPACK, dopo un certo tempo ripete semplicemente il DHCPREQUEST.
Tabella della struttura del pacchetto DHCPACK
Posizione nel pacchetto
Nome del valore (comune)
Esempio
Rappresentazione
Byte
Spiegazione
1
Richiesta di avvio
2
Esadecimale
1
Tipo di messaggio. 1 — richiesta dal cliente al server, 2 — risposta dal server al cliente
2
Tipo hardware
1
Esadecimale
1
Tipo di indirizzo hardware, in questo protocollo 1 — MAC
3
Lunghezza dell'indirizzo hardware
6
Esadecimale
1
Lunghezza dell'indirizzo MAC del dispositivo
4
Salti
1
Esadecimale
1
Numero di percorsi intermedi
5
ID transazione
23:cf:de:1d
Esadecimale
4
Identificatore unico della transazione. Generato dal cliente all'inizio dell'operazione di richiesta.
7
Secondi trascorsi
0
Esadecimale
4
Tempo in secondi dall'inizio del processo di acquisizione dell'indirizzo.
9
Flag Bootp
8000
Esadecimale
2
Alcuni flag che possono essere impostati come indicazione dei parametri del protocollo. In questo caso è stato impostato "broadcast"
11
Indirizzo IP del cliente
0.0.0.0
Riga
4
Indirizzo IP del cliente (se presente)
15
Il tuo indirizzo IP del cliente
172.16.134.61
Riga
4
Indirizzo IP proposto dal server (se presente)
19
Indirizzo IP del server successivo
0.0.0.0
Riga
4
Indirizzo IP del server (se noto)
23
Indirizzo IP dell'agente di relay
172.16.114.41
Riga
4
Indirizzo IP dell'agente di relay (ad esempio uno switch).
27
Indirizzo MAC del cliente
14:d6:4d:a7:c9:55
Esadecimale
6
Indirizzo MAC del mittente del pacchetto (cliente).
31
Padding dell'indirizzo hardware del cliente
Esadecimale
10
Spazio riservato. Di solito riempito di zeri.
41
Nome host del server
Riga
64
Nome del server DHCP. Di solito non viene trasmesso.
105
Nome del file di avvio
Riga
128
Nome del file sul server, usato dalle stazioni senza disco durante il caricamento
235
Magic cookie
63:82:53:63
Esadecimale
4
Il numero «magico» attraverso il quale è possibile determinare, tra l'altro, che questo pacchetto appartiene al protocollo DHCP
Opzioni DHCP. Possono essere in qualsiasi ordine
236
Numero dell'opzione
53
Dec
3
Opzione 53, che definisce il tipo di pacchetto DHCP 5 — DHCPACK
Lunghezza dell'opzione
1
Dec
1
Valore dell'opzione
5
Dec
1
Numero dell'opzione
1
Dec
1
Opzione che offre al client DHCP la maschera di rete
Lunghezza dell'opzione
4
Dec
1
Valore dell'opzione
255.255.224.0
Riga
4
Numero dell'opzione
3
Dec
1
Opzione che offre al client DHCP il gateway predefinito
Lunghezza dell'opzione
4
Dec
1
Valore dell'opzione
172.16.12.1
Riga
4
Numero dell'opzione
6
Dec
1
Opzione che offre al client DHCP il DNS
Lunghezza dell'opzione
4
Dec
1
Valore dell'opzione
8.8.8.8
Riga
4
Numero dell'opzione
51
Dec
1
Tempo di vita dei parametri di rete assegnati in secondi, dopo il quale il client DHCP deve richiederli di nuovo
Lunghezza dell'opzione
4
Dec
1
Valore dell'opzione
86400
Dec
4
Numero dell'opzione
82
Dec
1
Opzione 82, ripete ciò che è stato ricevuto nel DHCPDISCOVER
Lunghezza dell'opzione
18
Dec
1
Valore dell'opzione
01:08:00:06:00
01:01:00:00:01
02:06:00:03:0f
26:4d:ec
Dec
18
Fine del pacchetto
255
Dec
1
255 simboleggia la fine del pacchetto
Installazione
L'installazione consiste fondamentalmente nell'installazione dei moduli Python necessari per il funzionamento. Si presume che MySQL sia già installato e configurato.
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
Creiamo il database MySQL, carichiamo il dump pydhcp.sql e configuriamo il file di configurazione.
Configurazione
Tutte le impostazioni del server sono contenute in un file di formato XML. File di riferimento:
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')Ora più nel dettaglio sui tag:
La sezione dhcpserver descrive le impostazioni principali per l'avvio del server, ossia:
- host — quale indirizzo IP ascolta il server sulla porta 67
- broadcast — quale IP è utilizzato come broadcast per DHCPOFFER e DHCPACK
- DHCPServer — quale IP ha il server DHCP
- LeaseTime — tempo di noleggio dell'indirizzo IP rilasciato
- ThreadLimit — quanti thread possono essere eseguiti contemporaneamente per l'elaborazione dei pacchetti UDP in arrivo sulla porta 67. Si presume che possa aiutare in progetti ad alta intensità di carico 😉
- defaultMask, defaultRouter, defaultDNS — ciò che viene offerto al cliente per impostazione predefinita, se l'IP è trovato nel database, ma non sono specificati parametri aggiuntivi
Sezione mysql:
host, username, password, basename — tutto parla da sé. La struttura approssimativa del database è pubblicata su
Sezione query: qui sono descritti i requisiti per ottenere OFFER/ACK:
- offer_count — numero di righe con requisiti che restituiscono risultati del tipo ip, mask, router, dns
- offer_n — stringa di richiesta. Se la restituzione è vuota, esegue il successivo requisito offer
- history_sql — richiesta che scrive ad esempio nella 'storia di autorizzazione' per il cliente
Nelle richieste possono partecipare qualsiasi variabile dalla sezione options o opzioni dal protocollo DHCP.
Sezione options. Qui è già più interessante. Qui possiamo creare variabili che possiamo utilizzare in seguito nella sezione query.
Ad esempio:
option_82_hex:sw_port1:20:22, questa riga di comando prende l'intera stringa ricevuta nella richiesta DHCP dell'opzione 82, in formato esadecimale, nell'intervallo dai 20 ai 22 byte inclusi e la mette in una nuova variabile sw_port1 (porta dello switch da cui è arrivata la richiesta)
opzione_82_hex:sw_mac:26:40, definiamo la variabile sw_mac, prendendo l'esadecimale dall'intervallo 26:40
È possibile visualizzare tutte le opzioni che possono essere utilizzate nelle richieste avviando il server con la chiave -d. Vedremo un log simile a questo:
-- è arrivato un pacchetto DHCPINFORM sulla porta 67, da 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'}Pertanto possiamo racchiudere qualsiasi variabile tra {} e sarà utilizzata nella richiesta SQL.
Registriamo per la storia quale indirizzo IP ha ricevuto il cliente:


Avvio del server
.\/pydhcpdb.py -d -c config.xml
— d modalità di output nella console DEBUG
— c <nome_file> file di configurazione
Analisi dei voli
E ora più in dettaglio sull'implementazione del server in Python. È una sofferenza. Python è stato studiato 'in corsa'. Molti aspetti sono stati fatti nello stile: 'wow, in qualche modo ho fatto funzionare questo'. Non sono affatto ottimizzati e sono stati mantenuti in questo modo principalmente a causa della poca esperienza nello sviluppo in Python. Mi soffermerò sui punti più interessanti dell'implementazione del server nel 'codice'.
Parser del file di configurazione XML
Viene utilizzato il modulo standard Python xml.dom. Sembra semplice, ma nella realizzazione è mancata notevolmente una documentazione adeguata e esempi online sull'uso di questo modulo.
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
Stranamente, la multithreading in Python è implementata in modo molto chiaro e semplice.
def PacketWork(data,addr):
...
# implementazione dell'analisi del pacchetto ricevuto e della risposta a esso
...
while True:
data, addr = udp_socket.recvfrom(1024) # aspettiamo un pacchetto UDP
thread = threading.Thread(target=PacketWork, args=(data,addr,)).start() # appena arriva - avviamo in background una funzione precedentemente definita PacketWork con i parametri
while threading.active_count() > gconfig["dhcp_ThreadLimit"]:
time.sleep(1) # se il numero di thread già avviati è maggiore di quello impostato, aspettiamo finché non diminuisceRicezione/invio del pacchetto DHCP
Per intercettare i pacchetti UDP che passano attraverso la scheda di rete, è necessario "attivare" il socket:
udp_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,socket.IPPROTO_UDP) udp_socket.bind((gconfig["dhcp_host"],67))
, dove i flag:
- AF_INET — indica che il formato dell'indirizzo sarà IP: porta. Può esserci anche AF_UNIX — dove l'indirizzo è specificato con il nome del file.
- SOCK_DGRAM — significa che riceviamo non un "pacchetto grezzo", ma già passato attraverso il firewall e con il pacchetto parzialmente troncato. Cioè, riceviamo solo il pacchetto UDP senza la "parte fisica" del pacchetto UDP. Se utilizzi il flag SOCK_RAW, sarà necessario analizzare anche questa "parte".
L'invio di un pacchetto può avvenire tramite broadcasting:
udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) #Passiamo il socket in modalità invio broadcasting
rz=udp_socket.sendto(packetack, (gconfig["broadcast"],68)), oppure all'indirizzo "da cui è arrivato il pacchetto":
udp_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # Passiamo il socket in modalità "molti ascoltatori"
rz=udp_socket.sendto(packetack, addr), dove SOL_SOCKET indica il "livello del protocollo" per impostare le opzioni,
, SO_BROADCAST è l'opzione che indica che il pacchetto è inviato "in broadcasting"
, SO_REUSEADDR è l'opzione che cambia il socket alla modalità "molti ascoltatori". Teoricamente, non è necessaria in questo caso, ma su uno dei server FreeBSD su cui ho testato, senza questa opzione il codice non funzionava.
Analisi del pacchetto DHCP
Qui Python mi è piaciuto davvero. Si scopre che "out of the box" consente di trattare il bytecode in modo molto flessibile. Permettendo di convertirlo facilmente in valori decimali, stringhe e hex — cioè ciò di cui abbiamo effettivamente bisogno per comprendere la struttura del pacchetto. Ad esempio, si può ottenere un intervallo di byte in HEX e nei byte semplici:
res["xidhex"]=data[4:8].hex()
res["xidbyte"]=data[4:8], imballare i byte in una struttura:
res["flags"]=pack('BB',data[10],data[11])Ottenere l'IP dalla struttura:
res["ciaddr"]=socket.inet_ntoa(pack('BBBB',data[12],data[13],data[14],data[15]));E viceversa:
res=res+socket.inet_pton(socket.AF_INET, gconfig["dhcp_Server"])
Questo è tutto 😉
Fonte: habr.com
