
Celem tego projektu było:
- Badanie protokołu DHCP w sieci IPv4
- Nauka Pythona (trochę więcej niż od zera 😉)
- wymiana serwera (mój fork), oryginał , który coraz trudniej skompilować na nowym systemie operacyjnym. Nie podoba mi się również, że nie mam możliwości „zmiany na już” pliku binarnego.
- uzyskanie działającego serwera DHCP z możliwością wyboru adresu IP abonenta na podstawie adresu mac abonenta lub zestawu mac switcha + port (Opcja 82)
- napisanie kolejnego roweru (O! To moja ulubiona czynność)
- otrzymanie krytyki za swoją niezdarność na Habrze (a lepiej zaproszenia) 😉
Wynik: działa 😉 Przetestowane na systemach FreeBSD i Ubuntu. Teoretycznie kod można przystosować do działania na każdym systemie operacyjnym, ponieważ w kodzie nie ma specyficznych uzależnień.
Ostrożnie! Dalej jest dużo.
Link do repozytorium dla tych, którzy chcą .
Proces instalacji, konfiguracji i używania wyniku „nauki materiału” jest dużo niżej, a poniżej trochę teorii na temat protokołu DHCP. Dla siebie. I dla historii 😉
Trochę teorii
Czym jest DHCP
To protokół sieciowy, który pozwala urządzeniu poznać swój adres IP (no i inne parametry, takie jak brama, DNS itp.) od serwera DHCP. Wymiana pakietów odbywa się za pomocą protokołu UDP. Ogólny zasada działania urządzenia przy żądaniu parametrów sieciowych jest następująca:
- Urządzenie (klient) wysyła rozgłoszeniowe zapytanie UDP (DHCPDISCOVER) w całej sieci z prośbą „ktoś, dajcie mi adres IP”. Zazwyczaj (ale nie zawsze) zapytanie następuje z portu 68 (źródło), a przeznaczenie – port 67 (przeznaczenie). Niektóre urządzenia wysyłają pakiety również z portu 67. W pakiecie DHCPDISCOVER zawarty jest adres MAC urządzenia klienta.
- Wszystkie serwery DHCP znajdujące się w sieci (a może ich być kilka) formułują ofertę DHCPOFFER z ustawieniami sieciowymi dla urządzenia, które wysłało DHCPDISCOVER i rozsyłają ją również rozgłoszeniowo w sieci. Identyfikacja, do kogo jest przeznaczony ten pakiet, odbywa się na podstawie adresu MAC klienta, dostarczonego wcześniej w zapytaniu DHCPDISCOVER.
- Klient akceptuje pakiety z ofertami ustawień sieciowych, wybiera najbardziej atrakcyjny (kryteria mogą być różne, na przykład czas dostawy pakietu, liczba pośrednich tras) i składa "oficjalną prośbę" DHCPREQUEST do serwera DHCP z wybranymi ustawieniami sieciowymi. W takim przypadku pakiet trafia już do konkretnego serwera DHCP.
- Serwer, który otrzymał DHCPREQUEST, wysyła pakiet w formacie DHCPACK, w którym ponownie wymienia ustawienia sieciowe przeznaczone dla danego klienta.

Ponadto istnieją pakiety DHCPINFORM, które są wysyłane przez klienta w celu poinformowania serwera DHCP, że "klient żyje" i korzysta z przydzielonych ustawień sieciowych. W tej implementacji serwer ignoruje te pakiety.
Format pakietów
Ogólnie rzecz biorąc, ramka pakietu Ethernet wygląda mniej więcej tak:
![]()
W naszym przypadku rozważymy jedynie dane zawarte w pakiecie UDP, pomijając nagłówki protokołów warstw OSI, a mianowicie strukturę DHCP:
DHCPDISCOVER
Tak więc proces uzyskiwania adresu IP dla urządzenia rozpoczyna się od wysłania przez klienta DHCP zapytania rozgłoszeniowego z portu 68 do 255.255.255.255:67. W tym pakiecie klient dołącza swój adres MAC oraz informację, co chce otrzymać od serwera DHCP. Struktura pakietu opisana jest w tabeli poniżej.
Tabela struktury pakietu DHCPDISCOVER
Pozycja w pakiecie
Nazwa wartości
Przykład
Reprezentacja
Bajt
Wyjaśnienie
1
Boot Request
1
Hex
1
Typ wiadomości. 1 — zapytanie od klienta do serwera, 2 — odpowiedź od serwera do klienta
2
Typ sprzętowy
1
Hex
1
Typ adresu sprzętowego, w tym protokole 1 — MAC
3
Długość adresu sprzętowego
6
Hex
1
Długość adresu MAC urządzenia
4
Hops
1
Hex
1
Liczba pośrednich tras
5
ID transakcji
23:cf:de:1d
Hex
4
Unikalny identyfikator transakcji. Generowany przez klienta na początku operacji żądania
7
Sekundy upływające
0
Hex
4
Czas w sekundach od rozpoczęcia procesu uzyskiwania adresu
9
Flagi Bootp
0
Hex
2
Różne flagi, które mogą być ustawiane jako wskaźniki parametrów protokołu
11
Adres IP klienta
0.0.0.0
Ciąg
4
Adres IP klienta (jeśli istnieje)
15
Twój adres IP klienta
0.0.0.0
Ciąg
4
Adres IP zaproponowany przez serwer (jeśli istnieje)
19
Adres IP następnego serwera
0.0.0.0
Ciąg
4
Adres IP serwera (jeśli znany)
23
Adres IP agenta przekazującego
172.16.114.41
Ciąg
4
Adres IP agenta retransmisji (np. switcha)
27
Adres MAC klienta
14:d6:4d:a7:c9:55
Hex
6
Adres MAC nadawcy pakietu (klienta)
31
Wyrównanie adresu sprzętowego klienta
Hex
10
Zarezerwowane miejsce. Zwykle wypełnione zerami
41
Nazwa hosta serwera
Ciąg
64
Nazwa serwera DHCP. Zwykle nie jest przesyłana
105
Nazwa pliku startowego
Ciąg
128
Nazwa pliku na serwerze używana przez stacje bezdyskowe podczas rozruchu
235
Magic cookie
63:82:53:63
Hex
4
„Magic” liczba, dzięki której można określić, że ten pakiet należy do protokołu DHCP
Opcje DHCP. Mogą występować w dowolnej kolejności
236
Numer opcji
53
Dec
1
Opcja 53, definiująca typ pakietu DHCP
1 — DHCPDISCOVER
3 — DHCPREQUEST
2 — DHCPOFFER
5 — DHCPACK
8 — DHCPINFORM
Długość opcji
1
Dec
1
Wartość opcji
1
Dec
1
Numer opcji
50
Dec
1
Jaki adres IP chce uzyskać klient
Długość opcji
4
Dec
1
Wartość opcji
172.16.134.61
Ciąg
4
Numer opcji
55
1
Parametry sieciowe żądane przez klienta. Skład może być różny
01 — Maska sieci
03 — Brama
06 — DNS
oc — Nazwa hosta
0f — nazwa domeny sieci
1c — adres rozgłoszeniowy (broadcast)
42 — nazwa serwera TFTP
79 — Klasa-less Static Route
Długość opcji
8
1
Wartość opcji
01:03:06:0c:0f:1c:42:79
8
Numer opcji
82
Dec
Opcja 82, w której przekazywany jest adres MAC urządzenia — retransmitera oraz dodatkowe wartości.
Najczęściej — port switcha, na którym działa końcowy klient DHCP. W tej opcji „wbudowane” są dodatkowe parametry. Pierwszy bajt — numer „podopcji”, drugi jej długość, a następnie jej wartość.
W tym przypadku w opcji 82, zawarte są podopcje:
Agent Circuit ID = 00:04:00:01:00:04, gdzie ostatnie dwa bajty to port klienta DHCP, z którego nadeszło żądanie
Agent Remote ID = 00:06:c8:be:19:93:11:48 — adres MAC retransmitera DHCP
Długość opcji
18
Dec
Wartość opcji
01:06
00:04:00:01:00:04
02:08
00:06:c8:be:19:93:11:48
Hex
Koniec pakietu
255
Dec
1
255 symbolizuje koniec pakietu
DHCPOFFER
Gdy serwer otrzymuje pakiet DHCPDISCOVER i widzi, że może coś zaproponować klientowi, formułuje odpowiedź — DHCPDISCOVER. Odpowiedź jest wysyłana na port, z którego przybyło zapytanie, jako broadcast, ponieważ w tym momencie klient nie ma jeszcze adresu IP, co oznacza, że może przyjąć pakiet tylko jeśli został wysłany jako rozgłoszeniowy. Klient rozpoznaje, że to pakiet dla niego po swoim adresie MAC w pakiecie, jak również po numerze transakcji, który generuje w momencie tworzenia pierwszego pakietu.
Tabela struktury pakietu DHCPOFFER
Pozycja w pakiecie
Nazwa wartości (powszechnie przyjęta)
Przykład
Reprezentacja
Bajt
Wyjaśnienie
1
Boot Request
1
Hex
1
Typ wiadomości. 1 — zapytanie od klienta do serwera, 2 — odpowiedź od serwera do klienta
2
Typ sprzętowy
1
Hex
1
Typ adresu sprzętowego, w tym protokole 1 — MAC
3
Długość adresu sprzętowego
6
Hex
1
Długość adresu MAC urządzenia
4
Hops
1
Hex
1
Liczba pośrednich tras
5
ID transakcji
23:cf:de:1d
Hex
4
Unikalny identyfikator transakcji. Generowany przez klienta na początku operacji żądania
7
Sekundy upływające
0
Hex
4
Czas w sekundach od rozpoczęcia procesu uzyskiwania adresu
9
Flagi Bootp
0
Hex
2
Jakieś flagi, które mogą być ustawiane jako wskazanie parametrów protokołu. W tym przypadku, 0 — oznacza typ zapytania Unicast
11
Adres IP klienta
0.0.0.0
Ciąg
4
Adres IP klienta (jeśli istnieje)
15
Twój adres IP klienta
172.16.134.61
Ciąg
4
Adres IP zaproponowany przez serwer (jeśli istnieje)
19
Adres IP następnego serwera
0.0.0.0
Ciąg
4
Adres IP serwera (jeśli znany)
23
Adres IP agenta przekazującego
172.16.114.41
Ciąg
4
Adres IP agenta retransmisji (np. switcha)
27
Adres MAC klienta
14:d6:4d:a7:c9:55
Hex
6
Adres MAC nadawcy pakietu (klienta)
31
Wyrównanie adresu sprzętowego klienta
Hex
10
Zarezerwowane miejsce. Zwykle wypełnione zerami
41
Nazwa hosta serwera
Ciąg
64
Nazwa serwera DHCP. Zwykle nie jest przesyłana
105
Nazwa pliku startowego
Ciąg
128
Nazwa pliku na serwerze używana przez stacje bezdyskowe podczas rozruchu
235
Magic cookie
63:82:53:63
Hex
4
„Magic” liczba, dzięki której można określić, że ten pakiet należy do protokołu DHCP
Opcje DHCP. Mogą występować w dowolnej kolejności
236
Numer opcji
53
Dec
1
Opcja 53, definiująca typ pakietu DHCP 2 — DHCPOFFER
Długość opcji
1
Dec
1
Wartość opcji
2
Dec
1
Numer opcji
1
Dec
1
Opcja oferująca klientowi DHCP maskę sieci
Długość opcji
4
Dec
1
Wartość opcji
255.255.224.0
Ciąg
4
Numer opcji
3
Dec
1
Opcja oferująca klientowi DHCP domyślną bramę
Długość opcji
4
Dec
1
Wartość opcji
172.16.12.1
Ciąg
4
Numer opcji
6
Dec
1
Opcja oferująca klientowi DHCP DNS
Długość opcji
4
Dec
1
Wartość opcji
8.8.8.8
Ciąg
4
Numer opcji
51
Dec
1
Czas życia wydanych parametrów sieciowych w sekundach, po którym klient DHCP powinien zażądać ich ponownie
Długość opcji
4
Dec
1
Wartość opcji
86400
Dec
4
Numer opcji
82
Dec
1
Opcja 82, powtarza to, co przyszło w DHCPDISCOVER
Długość opcji
18
Dec
1
Wartość opcji
01:08:00:06:00
01:01:00:00:01
02:06:00:03:0f
26:4d:ec
Dec
18
Koniec pakietu
255
Dec
1
255 symbolizuje koniec pakietu
DHCPREQUEST
Po tym, jak klient otrzyma DHCPOFFER, tworzy pakiet z żądaniem parametrów sieciowych, kierując go nie do wszystkich serwerów DHCP w sieci, a tylko do jednego konkretnego, którego oferta DHCPOFFER najbardziej mu się spodobała. Kryteria „spodobało się” mogą być różne i zależą od implementacji klienta DHCP. Adresat żądania określany jest za pomocą adresu MAC serwera DHCP. Pakiet DHCPREQUEST może być również wysłany przez klienta bez wcześniejszego utworzenia DHCPDISCOVER, jeśli adres IP na serwerze był już wcześniej uzyskany.
Tabela struktury pakietu DHCPREQUEST
Pozycja w pakiecie
Nazwa wartości (powszechnie przyjęta)
Przykład
Reprezentacja
Bajt
Wyjaśnienie
1
Boot Request
1
Hex
1
Typ wiadomości. 1 — zapytanie od klienta do serwera, 2 — odpowiedź od serwera do klienta
2
Typ sprzętowy
1
Hex
1
Typ adresu sprzętowego, w tym protokole 1 — MAC
3
Długość adresu sprzętowego
6
Hex
1
Długość adresu MAC urządzenia
4
Hops
1
Hex
1
Liczba pośrednich tras
5
ID transakcji
23:cf:de:1d
Hex
4
Unikalny identyfikator transakcji. Generowany przez klienta na początku operacji żądania
7
Sekundy upływające
0
Hex
4
Czas w sekundach od rozpoczęcia procesu uzyskiwania adresu
9
Flagi Bootp
8000
Hex
2
Niektóre flagi, które mogą być ustawiane jako wskazanie parametrów protokołu. W tym przypadku ustawiona jest „broadcast”
11
Adres IP klienta
0.0.0.0
Ciąg
4
Adres IP klienta (jeśli istnieje)
15
Twój adres IP klienta
172.16.134.61
Ciąg
4
Adres IP zaproponowany przez serwer (jeśli istnieje)
19
Adres IP następnego serwera
0.0.0.0
Ciąg
4
Adres IP serwera (jeśli znany)
23
Adres IP agenta przekazującego
172.16.114.41
Ciąg
4
Adres IP agenta retransmisji (np. switcha)
27
Adres MAC klienta
14:d6:4d:a7:c9:55
Hex
6
Adres MAC nadawcy pakietu (klienta)
31
Wyrównanie adresu sprzętowego klienta
Hex
10
Zarezerwowane miejsce. Zwykle wypełnione zerami
41
Nazwa hosta serwera
Ciąg
64
Nazwa serwera DHCP. Zwykle nie jest przesyłana
105
Nazwa pliku startowego
Ciąg
128
Nazwa pliku na serwerze używana przez stacje bezdyskowe podczas rozruchu
235
Magic cookie
63:82:53:63
Hex
4
„Magic” liczba, dzięki której można określić, że ten pakiet należy do protokołu DHCP
Opcje DHCP. Mogą występować w dowolnej kolejności
236
Numer opcji
53
Dec
3
Opcja 53 definiująca typ pakietu DHCP 3 — DHCPREQUEST
Długość opcji
1
Dec
1
Wartość opcji
3
Dec
1
Numer opcji
61
Dec
1
Identyfikator klienta: 01 (dla Ethernetu) + adres MAC klienta
Długość opcji
7
Dec
1
Wartość opcji
01:2c:ab:25:ff:72:a6
Hex
7
Numer opcji
60
Dec
„Identifier klasy dostawcy”. W moim przypadku informuje o wersji klienta DHCP. Inne urządzenia mogą zwracać coś innego. Na przykład Windows raportuje MSFT 5.0
Długość opcji
11
Dec
Wartość opcji
udhcp 0.9.8
Ciąg
Numer opcji
55
1
Parametry sieciowe żądane przez klienta. Skład może być różny
01 — Maska sieci
03 — Brama
06 — DNS
oc — Nazwa hosta
0f — nazwa domeny sieci
1c — adres rozgłoszeniowy (broadcast)
42 — nazwa serwera TFTP
79 — Klasa-less Static Route
Długość opcji
8
1
Wartość opcji
01:03:06:0c:0f:1c:42:79
8
Numer opcji
82
Dec
1
Opcja 82, powtarza to, co przyszło w DHCPDISCOVER
Długość opcji
18
Dec
1
Wartość opcji
01:08:00:06:00
01:01:00:00:01
02:06:00:03:0f
26:4d:ec
Dec
18
Koniec pakietu
255
Dec
1
255 symbolizuje koniec pakietu
DHCPACK
Potwierdzeniem, że „tak, to na pewno twój adres IP, i więcej go nikomu nie wydam” od serwera DHCP jest pakiet w formacie DHCPACK wysłany do klienta. Podobnie jak inne pakiety, jest również wysyłany w sposób rozgłoszeniowy. Chociaż w poniższym kodzie serwera DHCP zrealizowanego w Pythonie, dla pewności powielam każdy pakiet rozgłoszeniowy, wysyłając pakiet na konkretny adres IP klienta, jeśli jest już znany. Serwer DHCP w ogóle nie martwi się, czy pakiet DHCPACK dotarł do klienta. Jeśli klient nie otrzyma DHCPACK, po pewnym czasie po prostu powtarza DHCPREQUEST.
Tabela struktury pakietu DHCPACK
Pozycja w pakiecie
Nazwa wartości (powszechnie przyjęta)
Przykład
Reprezentacja
Bajt
Wyjaśnienie
1
Boot Request
2
Hex
1
Typ wiadomości. 1 — zapytanie od klienta do serwera, 2 — odpowiedź od serwera do klienta
2
Typ sprzętowy
1
Hex
1
Typ adresu sprzętowego, w tym protokole 1 — MAC
3
Długość adresu sprzętowego
6
Hex
1
Długość adresu MAC urządzenia
4
Hops
1
Hex
1
Liczba pośrednich tras
5
ID transakcji
23:cf:de:1d
Hex
4
Unikalny identyfikator transakcji. Generowany przez klienta na początku operacji żądania
7
Sekundy upływające
0
Hex
4
Czas w sekundach od rozpoczęcia procesu uzyskiwania adresu
9
Flagi Bootp
8000
Hex
2
Niektóre flagi, które mogą być ustawiane jako wskazanie parametrów protokołu. W tym przypadku ustawiona jest „broadcast”
11
Adres IP klienta
0.0.0.0
Ciąg
4
Adres IP klienta (jeśli istnieje)
15
Twój adres IP klienta
172.16.134.61
Ciąg
4
Adres IP zaproponowany przez serwer (jeśli istnieje)
19
Adres IP następnego serwera
0.0.0.0
Ciąg
4
Adres IP serwera (jeśli znany)
23
Adres IP agenta przekazującego
172.16.114.41
Ciąg
4
Adres IP agenta retransmisji (np. switcha)
27
Adres MAC klienta
14:d6:4d:a7:c9:55
Hex
6
Adres MAC nadawcy pakietu (klienta)
31
Wyrównanie adresu sprzętowego klienta
Hex
10
Zarezerwowane miejsce. Zwykle wypełnione zerami
41
Nazwa hosta serwera
Ciąg
64
Nazwa serwera DHCP. Zwykle nie jest przesyłana
105
Nazwa pliku startowego
Ciąg
128
Nazwa pliku na serwerze używana przez stacje bezdyskowe podczas rozruchu
235
Magic cookie
63:82:53:63
Hex
4
„Magic” liczba, dzięki której można określić, że ten pakiet należy do protokołu DHCP
Opcje DHCP. Mogą występować w dowolnej kolejności
236
Numer opcji
53
Dec
3
Opcja 53 definiująca typ pakietu DHCP 5 — DHCPACK
Długość opcji
1
Dec
1
Wartość opcji
5
Dec
1
Numer opcji
1
Dec
1
Opcja oferująca klientowi DHCP maskę sieci
Długość opcji
4
Dec
1
Wartość opcji
255.255.224.0
Ciąg
4
Numer opcji
3
Dec
1
Opcja oferująca klientowi DHCP domyślną bramę
Długość opcji
4
Dec
1
Wartość opcji
172.16.12.1
Ciąg
4
Numer opcji
6
Dec
1
Opcja oferująca klientowi DHCP DNS
Długość opcji
4
Dec
1
Wartość opcji
8.8.8.8
Ciąg
4
Numer opcji
51
Dec
1
Czas życia wydanych parametrów sieciowych w sekundach, po którym klient DHCP powinien zażądać ich ponownie
Długość opcji
4
Dec
1
Wartość opcji
86400
Dec
4
Numer opcji
82
Dec
1
Opcja 82, powtarza to, co przyszło w DHCPDISCOVER
Długość opcji
18
Dec
1
Wartość opcji
01:08:00:06:00
01:01:00:00:01
02:06:00:03:0f
26:4d:ec
Dec
18
Koniec pakietu
255
Dec
1
255 symbolizuje koniec pakietu
Instalacja
Instalacja polega na zainstalowaniu modułów Pythona niezbędnych do działania. Zakłada się, że MySQL jest już zainstalowany i skonfigurowany.
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
Tworzymy bazę danych MySQL, wgrywamy do niej dump pydhcp.sql, konfiguruje plik konfiguracyjny.
Konfiguracja
Wszystkie ustawienia serwera znajdują się w pliku w formacie xml. Plik wzorcowy:
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')Teraz bliżej przyjrzymy się tagom:
Sekcja dhcpserver opisuje podstawowe ustawienia uruchomienia serwera, a mianowicie:
- host — jaki adres IP nasłuchuje serwer na porcie 67
- broadcast — jaki adres IP jest broadcastem dla DHCPOFFER i DHCPACK
- DHCPServer — jaki adres IP ma serwer DHCP
- LeaseTime — czas leasingu przydzielonego adresu IP
- ThreadLimit — ile jednoczesnych wątków jest uruchomionych do obsługi przychodzących pakietów UDP na porcie 67. Przypuszcza się, że pomoże to w projektach o wysokim obciążeniu 😉
- defaultMask,defaultRouter,defaultDNS — to, co jest oferowane abonentowi domyślnie, jeśli adres IP w bazie został znaleziony, ale dodatkowe parametry dla niego nie są określone
Sekcja mysql:
host,username,password,basename — wszystko mówi samo za siebie. Przybliżona struktura bazy danych została udostępniona na
Sekcja query: tutaj opisywane są zapytania do uzyskania OFFER/ACK:
- offer_count — liczba wierszy z zapytaniami, które zwracają wynik w postaci ip,mask,router,dns
- offer_n — string zapytania. Jeśli zwrot jest pusty, to wykonuje kolejny wniosek offer
- history_sql — zapytanie zapisujące na przykład w «historii autoryzacji» abonenta
W zapytaniach mogą brać udział dowolne zmienne z sekcji options lub opcje z protokołu DHCP.
Sekcja options. Tutaj już bardziej interesująco. Możemy tworzyć zmienne, które możemy wykorzystać później w sekcji query.
Na przykład:
option_82_hex:sw_port1:20:22, to polecenie weź całą linię przesłaną w zapytaniu DHCP opcji 82, w formacie hex, w zakresie od 20 do 22 bajta włącznie i umieść ją w nowej zmiennej sw_port1 (port switcha, z którego nadeszło zapytanie)
option_82_hex:sw_mac:26:40, definiujemy zmienną sw_mac, biorąc hex z zakresu 26:40
Można zobaczyć wszystkie możliwe opcje, które można wykorzystać w zapytaniach, uruchamiając serwer z kluczem -d. Zobaczymy mniej więcej taki log:
--przyszedł pakiet DHCPINFORM na port 67, od 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'}Zatem możemy każdą zmienną otoczyć w {} i będzie używana w zapytaniu SQL.
Utrwalimy dla historii, że adres IP, który klient otrzymał:


Uruchamianie serwera
.\/pydhcpdb.py -d -c config.xml
— d tryb wyjścia na konsolę DEBUG
— c <nazwa_pliku> plik konfiguracyjny
Analiza lotów
A teraz szczegółowo o implementacji serwera w Pythonie. To ból. Python był uczony «w locie». Wiele momentów zrealizowano w stylu: «wow, jakoś zrobiłem, co działa». Całkowicie nieoptymalne, pozostawione głównie z powodu małego doświadczenia w programowaniu w pythonie. Skupię się na najbardziej interesujących aspektach implementacji serwera w «kodzie».
Parser pliku konfiguracyjnego XML
Wykorzystuje standardowy moduł Python xml.dom. Wydaje się proste, ale przy implementacji zauważalnie brakowało sensownej dokumentacji i przykładów w sieci z użyciem tego modułu.
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)Wielowątkowość
Co dziwne, wielowątkowość w Pythonie jest realizowana w sposób bardzo zrozumiały i prosty.
def PacketWork(data, addr):
...
# implementacja analizy przychodzącego pakietu oraz odpowiedzi na niego
...
while True:
data, addr = udp_socket.recvfrom(1024) # czekamy na pakiet UDP
thread = threading.Thread(target=PacketWork, args=(data, addr,)).start() # kiedy przychodzi - uruchamiamy w tle wcześniej określoną funkcję PacketWork z parametrami
while threading.active_count() > gconfig["dhcp_ThreadLimit"]:
time.sleep(1) # jeśli liczba uruchomionych wątków jest większa niż w ustawieniach, czekamy aż ich liczba spadnieOdbieranie/wysyłanie pakietu DHCP
Aby przechwycić pakiety UDP przechodzące przez kartę sieciową, należy „podnieść” socket:
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) udp_socket.bind((gconfig["dhcp_host"], 67))
, gdzie flagi:
- AF_INET — oznacza, że format adresu będzie IP: port. Może być również AF_UNIX — gdzie adres podaje się jako nazwę pliku.
- SOCK_DGRAM oznacza, że otrzymujemy nie „surową paczkę”, ale taką, która przeszła przez zaporę, i częściowo obciętą. Tzn. otrzymujemy tylko pakiet UDP bez „fizycznego” elementu opakowania pakietu UDP. Jeśli użyjemy flagi SOCK_RAW, to będziemy musieli również sparsować to „opakowanie”.
Wysyłka pakietu może być jako broadcasting:
udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) # przestawiamy socket w tryb wysyłania broadcastu
rz=udp_socket.sendto(packetack, (gconfig["broadcast"],68)), a także na adres, z którego „przyszedł pakiet”:
udp_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # przestawiamy socket w tryb "wielu słuchaczy"
rz=udp_socket.sendto(packetack, addr), gdzie SOL_SOCKET oznacza „poziom protokołu” dla ustawienia opcji,
, SO_BROADCAST to opcja, że pakiet wysyłamy jako „broadcast”
, SO_REUSEADDR to opcja przestawiająca socket w tryb „wielu słuchaczy”. Teoretycznie nie jest to konieczne w tym przypadku, ale na jednym z serwerów FreeBSD, na którym testowałem, bez tej opcji kod nie działał.
Analiza pakietu DHCP
Tutaj naprawdę spodobał mi się Python. Okazuje się, że z „pudełka” pozwala on na dość swobodne obchodzenie się z kodem bajtowym. Pozwala go bardzo łatwo przekształcać na wartości dziesiętne, ciągi oraz hex — tzn. to, czego potrzebujemy, aby zrozumieć strukturę pakietu. Można na przykład uzyskać zakres bajtów w HEX oraz po prostu w bajtach:
res["xidhex"]=data[4:8].hex()
res["xidbyte"]=data[4:8], zapakować bajty w strukturę:
res["flags"]=pack('BB',data[10],data[11])Uzyskać IP ze struktury:
res["ciaddr"]=socket.inet_ntoa(pack('BBBB',data[12],data[13],data[14],data[15]));I odwrotnie:
res=res+socket.inet_pton(socket.AF_INET, gconfig["dhcp_Server"])
Na tym kończymy 😉
Źródło: habr.com
