Server Ersatz DB2DHCP (meng Gabel), original hei, wat ëmmer méi schwéier gëtt fir den neien OS ze montéieren. An ech hunn net gär datt et e Binär ass datt et kee Wee ass fir "de Moment ze änneren"
en funktionnéierenden DHCP-Server ze kréien mat der Fäegkeet fir d'IP Adress vum Abonnent mat dem Mac vum Abonnent ze wielen oder Mac + Port Kombinatioun ze wiesselen (Optioun 82)
en anere Vëlo schreiwen (Oh! dat ass meng Liiblingsaktivitéit)
Kommentaren iwwer Är Veräinshëllef op Habrahabr kréien (oder besser nach eng Invitatioun) 😉
Resultat: et funktionnéiert 😉 Getest op FreeBSD an Ubuntu OS. Theoretesch kann de Code gefrot ginn ënner all OS ze schaffen, well Et schéngt keng spezifesch Bindungen am Code ze sinn.
Virsiichteg! Et gëtt nach vill méi ze kommen.
Link zum Repository fir Amateuren "Touch lieweg".
De Prozess fir d'Installatioun, d'Konfiguratioun an d'Benotzung vum Resultat vum "Hardware studéieren" ass vill méi niddereg, an dann eng kleng Theorie iwwer den DHCP Protokoll. Fir mech selwer. A fir d'Geschicht 😉
Eng kleng Theorie
Wat ass DHCP
Dëst ass en Netzwierkprotokoll deen en Apparat erlaabt seng IP Adress (an aner Parameteren wéi Gateway, DNS, etc.) vun engem DHCP Server erauszefannen. Pakete ginn mam UDP Protokoll ausgetosch. Den allgemenge Prinzip vun der Operatioun vum Apparat beim Ufro vun Netzwierkparameter ass wéi follegt:
Den Apparat (Client) schéckt eng UDP Broadcast Ufro (DHCPDISCOVER) am ganzen Netz mat der Ufro "gutt, een gitt mir eng IP Adress." Ausserdeem, normalerweis (awer net ëmmer) geschitt d'Ufro vum Hafen 68 (Quell), an d'Destinatioun ass den Hafen 67 (Destinatioun). E puer Apparater schécken och Pakete vum Port 67. D'MAC Adress vum Client Apparat ass am DHCPDISCOVER Paket abegraff.
All DHCP Server um Netz läit (an et kann e puer vun hinnen ginn) Form eng DHCPOFFER Offer mat Reseau Astellunge fir den Apparat, datt DHCPDISCOVER geschéckt, an och et iwwer d'Netz Emissioun. D'Identifikatioun fir wien dëse Pak geduecht ass baséiert op der MAC Adress vum Client virdru an der DHCPDISCOVER Ufro geliwwert.
De Client akzeptéiert Pakete mat Virschléi fir Netz Astellungen, wielt déi attraktivst (d'Critèrë kënnen ënnerschiddlech sinn, zum Beispill d'Zäit vun der Paket Liwwerung, d'Zuel vun den Zwëschenrouten), a mécht eng "offiziell Ufro" DHCPREQUEST mat den Netzwierkastellungen vum DHCP Server deen et gär huet. An dësem Fall geet de Paket op e spezifeschen DHCP Server.
De Server, deen den DHCPREQUEST krut, schéckt en DHCPACK-Format Paket, an deem et nach eng Kéier d'Netzwierk Astellunge virgesi fir dëse Client opgezielt huet
Zousätzlech ginn et DHCPINFORM Päckchen, déi vum Client kommen, an den Zweck vun deem ass den DHCP-Server z'informéieren datt de "Client lieweg ass" a benotzt déi erausginn Netzwierkastellungen. An der Ëmsetzung vun dësem Server ginn dës Pakete ignoréiert.
Package Format
Am Allgemengen gesäit en Ethernet Paketframe sou eppes aus:
An eisem Fall wäerte mir nëmmen d'Donnéeën direkt aus dem Inhalt vum UDP Paket berücksichtegen, ouni OSI Layer Protokoll Header, nämlech d'DHCP Struktur:
DHCP DISCOVER
Also, de Prozess fir eng IP Adress fir en Apparat ze kréien fänkt mat dem DHCP Client un, deen eng Sendungsufro vum Hafen 68 op 255.255.255.255:67 schéckt. An dësem Package enthält de Client seng MAC Adress, wéi och wat et genee vum DHCP Server wëll kréien. D'Packagestruktur gëtt an der Tabell hei ënnen beschriwwen.
DHCPDISCOVER Packet Struktur Table
Positioun am Package
Wäert Numm
Beispill:
Aféierung
Byt
Erklärung
1
Boot Ufro
1
Hex
1
Message Typ. 1 - Ufro vum Client op Server, 2 - Äntwert vum Server op Client
2
Hardware Typ
1
Hex
1
Typ vun Hardware Adress, an dësem Protokoll 1 - MAC
Optioun Zuel
50
Dez
1
Wéi eng IP Adress wëll de Client kréien?
Optioun Längt
4
Dez
1
Optioun Wäert
172.16.134.61
Linn
4
Optioun Zuel
55
1
Netzwierkparameter gefrot vum Client. Zesummesetzung kann variéieren
01 - Netzwierk Mask
03 - Gateway
06 - DNS
oc - Hostnumm
0f - Netzwierk Domain Numm
1c - Adress vun der Sendungsufro (Sendung)
42 - TFTP Server Numm
79 - Classless statesch Route
Optioun Längt
8
1
Optioun Wäert
01:03:06:0c:0f:1c:42:79
8
Optioun Zuel
82
Dez
Optioun 82, déi d'MAC Adress vum Repeaterapparat an e puer zousätzlech Wäerter iwwerdréit.
Meeschtens ass dëst den Hafen vum Schalter op deem den Enn DHCP Client leeft. Dës Optioun enthält zousätzlech Parameteren. Den éischte Byte ass d'Zuel vun der "Ënneroptioun", déi zweet ass seng Längt, dann säi Wäert.
An dësem Fall, an der Optioun 82, sinn d'Ënneroptiounen nestéiert:
Agent Circuit ID = 00:04:00:01:00:04, wou déi lescht zwee Bytes den DHCP Clientport sinn, aus deem d'Ufro koum
Agent Remote ID = 00:06:c8:be:19:93:11:48 - MAC Adress vum DHCP Repeater Apparat
Enn vum Pak
255
Dez
1
255 symboliséiert d'Enn vum Pak
DHCPOFFER
Soubal de Server den DHCPDISCOVER Paket kritt a wann e gesäit datt et dem Client eppes vum ugefrote kann ubidden, da generéiert en eng Äntwert dofir - DHCPDISCOVER. D'Äntwert gëtt un den Hafen geschéckt "vu wou et koum", iwwer Sendung, well am Moment huet de Client nach keng IP Adress, dofir kann en de Paket nëmmen akzeptéieren wann et per Emissioun geschéckt gëtt. De Client erkennt datt dëst e Package fir hien ass duerch seng MAC Adress am Package, souwéi d'Transaktiounsnummer déi hien generéiert zur Zäit wou den éischte Package erstallt gëtt.
DHCPOFFER Packet Struktur Table
Positioun am Package
Numm vum Wäert (gemeinsam)
Beispill:
Aféierung
Byt
Erklärung
1
Boot Ufro
1
Hex
1
Message Typ. 1 - Ufro vum Client op Server, 2 - Äntwert vum Server op Client
2
Hardware Typ
1
Hex
1
Typ vun Hardware Adress, an dësem Protokoll 1 - MAC
Enn vum Pak
255
Dez
1
255 symboliséiert d'Enn vum Pak
DHCPREQUEST
Nodeems de Client DHCPOFFER kritt, formt hien e Paket deen Netzparameter ufroen net un all DHCP Server am Netz, awer nëmmen un engem spezifeschen, deem seng DHCPOFFER Offer hien am meeschte "gefällt". D'"wéi" Critèren kënnen ënnerschiddlech sinn an ofhängeg vun der DHCP-Implementatioun vum Client. Den Empfänger vun der Ufro gëtt mat der MAC Adress vum DHCP Server spezifizéiert. Och en DHCPREQUEST Paket kann vum Client geschéckt ginn ouni éischt DHCPDISCOVER ze generéieren, wann d'IP Adress vum Server scho virdru kritt gouf.
DHCPREQUEST Packet Struktur Table
Positioun am Package
Numm vum Wäert (gemeinsam)
Beispill:
Aféierung
Byt
Erklärung
1
Boot Ufro
1
Hex
1
Message Typ. 1 - Ufro vum Client op Server, 2 - Äntwert vum Server op Client
2
Hardware Typ
1
Hex
1
Typ vun Hardware Adress, an dësem Protokoll 1 - MAC
"Vendor Class Identifier". A mengem Fall bericht et d'DHCP Client Versioun. Vläicht ginn aner Apparater eppes anescht zréck. Windows zum Beispill bericht MSFT 5.0
Optioun Längt
11
Dez
Optioun Wäert
uhcp 0.9.8
Linn
Optioun Zuel
55
1
Netzwierkparameter gefrot vum Client. Zesummesetzung kann variéieren
01 - Netzwierk Mask
03 - Gateway
06 - DNS
oc - Hostnumm
0f - Netzwierk Domain Numm
1c - Adress vun der Sendungsufro (Sendung)
42 - TFTP Server Numm
79 - Classless statesch Route
Optioun Längt
8
1
Optioun Wäert
01:03:06:0c:0f:1c:42:79
8
Optioun Zuel
82
Dez
1
Optioun 82, widderhëlt wat an DHCPDISCOVER koum
Enn vum Pak
255
Dez
1
255 symboliséiert d'Enn vum Pak
DHCPACK
Als Bestätegung datt "jo, dat ass richteg, dëst ass Är IP Adress, an ech ginn et net un engem aneren" vum DHCP Server, e Paket am DHCPACK Format vum Server op de Client servéiert. Et gëtt Sendung geschéckt grad wéi aner Pakete. Obwuel, am Code hei ënnendrënner fir en DHCP-Server, deen am Python implementéiert ass, just am Fall, duplizéieren ech all Broadcast-Ufro andeems Dir e Paket un e spezifesche Client IP schéckt, wann et scho bekannt ass. Ausserdeem ass den DHCP Server guer net egal ob den DHCPACK Paket de Client erreecht huet. Wann de Client net DHCPACK kritt, dann widderhëlt et no enger Zäit einfach DHCPREQUEST
DHCPACK Packet Struktur Dësch
Positioun am Package
Numm vum Wäert (gemeinsam)
Beispill:
Aféierung
Byt
Erklärung
1
Boot Ufro
2
Hex
1
Message Typ. 1 - Ufro vum Client op Server, 2 - Äntwert vum Server op Client
2
Hardware Typ
1
Hex
1
Typ vun Hardware Adress, an dësem Protokoll 1 - MAC
Enn vum Pak
255
Dez
1
255 symboliséiert d'Enn vum Pak
Kader
D'Installatioun besteet eigentlech aus der Installatioun vun de Python-Module fir d'Aarbecht néideg. Et gëtt ugeholl datt MySQL scho installéiert a konfiguréiert ass.
Mir kreéieren eng MySQL-Datebank, lued den pydhcp.sql Dump an et erop, a konfiguréieren d'Konfiguratiounsdatei.
Configuratioun
All Server Astellunge sinn an enger XML Datei. Referenzdatei:
1.0 0.0.0.0 255.255.255.255 192.168.0.71 8600h 1 255.255.255.0 192.168.0.1 8.8.8.8 localhost testen testen pydhcp option_82_hex:sw_port1:20:22 option_82_hex:sw_port2:16:18 option_82_hex:sw_mac:26:40 3 wielt IP, Mask, Router, dns vu Benotzer wou upper(mac)=upper('{option_1_AgentRemoteId_hex}') an upper(port)=upper('{option_82_AgentCircuitId_port_hex}') wielt IP, Mask, Router, dns vu Benotzer wou upper(mac)=upper('{sw_mac}') an upper(port)=upper('{sw_port82}') wielt IP, Mask, Router, dns vu Benotzer wou upper(mac)=upper('{ClientMacAddress}') setzen an d'Geschicht (id,dt,mac,ip,comment) Wäerter (null,now(),'{ClientMacAddress}','{RequestedIpAddress}','DHCPACK/INFORM')
Elo méi detailléiert iwwer d'Tags:
D'dhcpserver Sektioun beschreift d'Basis Astellunge fir de Server ze starten, nämlech:
Host - wéi eng IP Adress de Server um Port 67 lauschtert
Broadcast - wéi eng IP d'Sendung fir DHCPOFFER an DHCPACK ass
DHCPServer - wat ass d'IP vum DHCP Server
LeaseTime Leasezäit vun der erausginn IP Adress
ThreadLimit - wéivill Threads lafen gläichzäiteg fir erakommen UDP-Päckchen um Port 67 ze veraarbechten.
defaultMask,defaultRouter,defaultDNS - wat gëtt dem Abonnent als Standard ugebueden wann eng IP an der Datebank fonnt gëtt, awer zousätzlech Parameteren sinn net spezifizéiert dofir
mysql Sektioun:
Host, Benotzernumm, Passwuert, Basename - alles schwätzt fir sech. Eng geschätzte Datebankstruktur gëtt op gepost GitHub
Query Sektioun: Ufroe fir OFFER / ACK ze kréien sinn hei beschriwwen:
offer_count - d'Zuel vun de Linnen mat Ufroen déi e Resultat zréckginn wéi IP, Mask, Router, dns
offer_n — Ufro String. Wann de Retour eidel ass, féiert dann déi folgend Offer Ufro aus
history_sql - eng Ufro déi zum Beispill un d'"Autorisatiounsgeschicht" fir en Abonnent schreift
Ufroe kënnen all Variabelen aus der Optiounssektioun oder Optiounen aus dem DHCP Protokoll enthalen.
Optiounen Rubrik. Dëst ass wou et méi interessant gëtt. Hei kënne mir Variablen erstellen déi mir spéider an der Ufro Sektioun kënne benotzen.
Zum Beispill:
option_82_hex:sw_port1:20:22
, dës Kommandozeil hëlt déi ganz Linn déi an der DHCP-Ufrooptioun 82 koum, am Hexformat, am Beräich vun 20 bis 22 Bytes inklusiv a setzt se an déi nei Variabel sw_port1 (Schalthafen vu wou d'Ufro koum)
option_82_hex:sw_mac:26:40
, definéiert d'sw_mac Variabel, hëlt d'Hex aus dem Beräich 26:40
Dir kënnt all méiglech Optiounen gesinn, déi an Ufroen benotzt kënne ginn andeems Dir de Server mam -d Schalter start. Mir wäerten eppes wéi dëse Log gesinn:
Deementspriechend kënne mir all Variabel an {} wéckelen an et gëtt an der SQL Ufro benotzt.
Loosst eis fir d'Geschicht notéieren datt de Client d'IP Adress krut:
De Server starten
./pydhcpdb.py -d -c config.xml
- d Konsol Ausgangsmodus DEBUG
- c <Dateiname> Konfiguratiounsdatei
Debriefing
An elo méi Detailer iwwer d'Ëmsetzung vum Server am Python. Et ass e Péng. Python gouf op der Flucht geléiert. Vill Momenter ginn am Stil vun "Wow, iergendwéi hunn ech et geschafft." Iwwerhaapt net optimiséiert, a lénks an dëser Form haaptsächlech wéinst wéineg Erfahrung an Python Entwécklung. Ech wäert op déi interessantst Aspekter vun der Serverimplementatioun am "Code" wunnen.
XML Konfiguratiounsdatei Parser
De Standard Python Modul xml.dom gëtt benotzt. Et schéngt einfach, awer während der Ëmsetzung gouf et e merkbare Mangel u kloer Dokumentatioun a Beispiller am Netz mat dësem Modul.
tree = minidom.parse(gconfig["config_file"]) mconfig=tree.getElementsByTagName("mysql") fir elem an mconfig: gconfig["mysql_host"]=elem.getElementsByTagName("host")[0].firstChild. gconfig["mysql_username"]=elem.getElementsByTagName("username")[0].firstChild.data gconfig["mysql_password"]=elem.getElementsByTagName("Passwuert")[0].firstChild.data gconfig_basenamesq]l =elem.getElementsByTagName("basename")[0].firstChild.data dconfig=tree.getElementsByTagName("dhcpserver") fir elem an 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"][" =elem.getElementsByTagName("defaultMask")[0].firstChild.data gconfig["dhcp_defaultRouter"]=elem.getElementsByTagName("defaultRouter")[0].firstChild.data gconfig["dhcp_defaultDget.Element"] defaultDNS")[0].firstChild.data qconfig=tree.getElementsByTagName("query") fir elem an qconfig: gconfig["offer_count"]=elem.getElementsByTagName("offer_count")[0].firstChild.data fir num an 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("optiounen") fir elem an Optiounen: node=elem.getElementsByTagName("Optioun") fir Optiounen am Node : optionsMod.append(options.firstChild.data)
Multithreading
Komesch genuch ass Multithreading am Python ganz kloer an einfach ëmgesat.
def PacketWork(data,addr): ... # Implementatioun vum Parsing vum erakommende Paket an Äntwert op et ... wärend True: data, addr = udp_socket.recvfrom(1024) # waart op den UDP Packet thread = threading.Thread( target=PacketWork, args=(data,addr,)).start() # wéi et koum - mir starten déi virdru definéiert PacketWork Funktioun am Hannergrond mat Parameteren iwwerdeems threading.active_count() >gconfig["dhcp_ThreadLimit"]: Zäit. schlofen(1) # wann d'Zuel Et gi scho méi thread wéi an den Astellungen, mir waarden bis et manner sinn
DHCP Paket kréien / schécken
Fir UDP Päckchen ze interceptéieren, déi duerch d'Netzwierkskaart kommen, musst Dir de Socket "erhiewen":
AF_INET - heescht datt d'Adressformat IP wäert sinn: port. Et kann och AF_UNIX sinn - wou d'Adress vum Dateinumm uginn ass.
SOCK_DGRAM - heescht datt mir net e "raw Paket" akzeptéieren, awer een deen schonn duerch d'Firewall passéiert ass, a mat engem deelweis ofgeschniddene Paket. Déi. mir kréien nëmmen en UDP Paket ouni de "kierperlechen" Bestanddeel vum UDP Paketwrapper. Wann Dir de SOCK_RAW Fändel benotzt, da musst Dir och dësen "Wrapper" parséieren.
E Paket schécken kann wéi eng Sendung sinn:
udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) #schalt de Socket op Broadcast Modus rz=udp_socket.sendto(packetack, (gconfig["Broadcast"],68))
, an op d'Adress "wou de Package hierkënnt":
udp_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # schalt de Socket op Multi-Listener Modus rz=udp_socket.sendto(packetack, addr)
, wou SOL_SOCKET den "Protokollniveau" bedeit fir Optiounen ze setzen,
, SO_BROADCAST Optioun datt den Helm Package "Broadcast" ass
,SO_REUSEADDR Optioun schalt de Socket op "vill Listener" Modus. An der Theorie ass et an dësem Fall onnéideg, awer op engem vun de FreeBSD Serveren, op deenen ech getest hunn, huet de Code net ouni dës Optioun geschafft.
Parsing vun engem DHCP Paket
Dëst ass wou ech de Python wierklech gär hunn. Et stellt sech eraus datt aus der Këscht et Iech erlaabt zimmlech flexibel mam Bytecode ze sinn. Erlaabt et ganz einfach an Dezimalwäerter ze iwwersetzen, Strings an Hex - d.h. dat ass wat mir tatsächlech brauchen fir d'Struktur vum Package ze verstoen. Also, zum Beispill, kënnt Dir eng Rei vu Bytes an HEX kréien a just Bytes: