Serwer DHCP+Mysql na Pythonie

Serwer DHCP+Mysql na Pythonie

Celem tego projektu było:

  • Badanie protokołu DHCP w sieci IPv4
  • Nauka Pythona (trochę więcej niż od zera 😉)
  • wymiana serwera DB2DHCP (mój fork), oryginał tutaj, 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ą „dotknąć na żywo”.

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:

  1. 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.
  2. 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.
  3. 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.
  4. Serwer, który otrzymał DHCPREQUEST, wysyła pakiet w formacie DHCPACK, w którym ponownie wymienia ustawienia sieciowe przeznaczone dla danego klienta.

Serwer DHCP+Mysql na Pythonie

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:

Serwer DHCP+Mysql na Pythonie

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 GitHub

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ł:

Serwer DHCP+Mysql na Pythonie

Serwer DHCP+Mysql na Pythonie

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 spadnie

Odbieranie/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

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster