
DHCP + Mysql server in Python

The purpose of this project was:
- Learning DHCP over an IPv4 Network
- Learning Python (a little more than from scratch;))
- replacing the DB2DHCP server (my fork), the original here , which is harder and harder to assemble under the new OS. Yes, and I do not like that the binary, which is not possible to "change right now"
- obtaining a working DHCP server with the ability to select the subscriber's IP address by subscriber's mac or a bunch of mac switches + port (Option 82)
- writing another bike (Oh! this is my favorite pastime)
- getting Lyuli about his squint on Habrahabr (or better invite);)
Result: it works;) Tested on FreeBSD and Ubuntu OS. Theoretically, the code can be asked to work under any OS, because there are no specific bindings in the code.
Caution! A lot further.
Link to the repository for fans to "touch alive . "
The process of installing, configuring, and using the result of the "study of the hardware" is much lower, and then a little theory on the DHCP protocol. For yourself. And for the story;)
A bit of theory
What is DHCP?
This is a network protocol that allows a device to find out its IP address (well, other parameters like a gateway, DNS, etc.) from a DHCP server. Packets are exchanged over UDP. The general principle of operation of the device when requesting network parameters is as follows:
- The device (client) sends out a UDP broadcast request (DHCPDISCOVER) across the network with the request, "Well, someone, give me an IP address." And usually (but not always) the request is from port 68 (source), and the destination is port 67 (destination). Some devices also send packets from port 67. Inside the DHCPDISCOVER packet, the MAC address of the client device is included.
- All DHCP servers located on the network (and there may be several) form for the device that sent DHCPDISCOVER a DHCPOFFER sentence with network settings, and it also broadcasts it over the network. Identification to whom this packet is intended goes to the MAC address of the client provided earlier in the DHCPDISCOVER request.
- The client receives packets with offers of network settings, selects the most attractive (the criteria can be different, for example, by the time of packet delivery, the number of intermediate routes), and makes the DHCP server “officially request” DHCPREQUEST with network settings. In this case, the packet goes to a specific DHCP server.
- The server that received DHCPREQUEST sends a DHCPACK packet in which it once again lists the network settings for this client

In addition, there are DHCPINFORM packets that come from the client, and the purpose of which is to inform the DHCP server that the "client is alive" and uses the issued network settings. In the implementation of this server, these packets are ignored.
Packet format
In general, the frame of the Ethernet packet looks something like this:

In our case, we will only consider data directly from the contents of the UDP packet, without the OSI layer protocol headers, namely the DHCP structure:
DHCPDISCOVER
So, the process of obtaining the IP address for the device begins with the fact that the DHCP client sends a broadcast request from port 68 to 255.255.255.255:67. In this package, the client includes its MAC address, as well as what it wants to receive from the DHCP server. The structure of the package is described in the table below.
DHCPDISCOVER packet structure table
Package Position | Value Name | Example | Representation | Byte | Explanation | |
1 | Boot request | 1 | Hex | 1 | Type of message. 1 - request from client to server, 2 - response from server to client | |
2 | Hardware type | 1 | Hex | 1 | Type of hardware address, in this protocol 1 - MAC | |
3 | Hardware adrees length | 6 | Hex | 1 | Device MAC Address Length | |
4 | Hops | 1 | Hex | 1 | Number of intermediate routes | |
5 | Transaction id | 23: cf: de: 1d | Hex | 4 | Unique Transaction Identifier. Generated by the client at the beginning of the request operation | |
7 | Second elapsed | 0 | Hex | 4 | Time in seconds since the beginning of the process of obtaining the address | |
9 | Bootp flags | 0 | Hex | 2 | Some flags that can be set as an indication of protocol parameters | |
eleven | Client IP address | 0.0.0.0 | Line | 4 | Client IP address (if any) | |
fifteen | Your client IP address | 0.0.0.0 | Line | 4 | IP address proposed by the server (if any) | |
19 | Next server IP address | 0.0.0.0 | Line | 4 | Server IP address (if known) | |
23 | Relay agent IP address | 172.16.114.41 | Line | 4 | Relay agent IP address (for example, a switch) | |
27 | Client MAC address | 14: d6: 4d: a7: c9: 55 | Hex | 6 | MAC address of the sender of the packet (client) | |
31 | Client hardware address padding | Hex | 10 | Reserved place. Usually zeros | ||
41 | Server host name | Line | 64 | The name of the DHCP server. Usually not transmitted | ||
105 | Boot file name | Line | 128 | The file name on the server used by diskless stations during boot | ||
235 | Magic cookie | 63: 82: 53: 63 | Hex | 4 | The "magic" number, by which incl. you can determine that this packet belongs to the DHCP protocol | |
DHCP options. Can go in any order | ||||||
236 | Option number | 53 | Dec | 1 | Option 53 specifying the type of packet DHCP 1 - DHCPDISCOVER 3 - DHCPREQUEST 2 - DHCPOFFER 5 - DHCPACK 8 - DHCPINFORM | |
Option length | 1 | Dec | 1 | |||
Option value | 1 | Dec | 1 | |||
Option number | fifty | Dec | 1 | What IP address does the client want to receive | ||
Option length | 4 | Dec | 1 | |||
Option value | 172.16.134.61 | Line | 4 | |||
Option number | 55 | 1 | The network parameters requested by the client. The composition can be different 01 - Network mask 03 - Gateway 06 - DNS oc - Host name 0f - network domain name 1c - broadcast request (broadcast) address 42 - TFTP server name 79 - Classless Static Route | |||
Option length | 8 | 1 | ||||
Option value | 01: 03: 06: 0c: 0f: 1c: 42: 79 | 8 | ||||
Option number | 82 | Dec | Option 82, in which the MAC address of the repeater device and some additional values are transmitted. Most often, the switch port on which the DHCP end client runs. Additional options are “embedded” in this option. The first byte is the number of the “suboption”, the second is its length, and then its value. In this case, option 82 contains the following options: Agent Circuit ID = 00: 04: 00: 01: 00: 04, where the last two bytes are the DHCP client port from which the Agent Remote ID = 00: 06: c8: be: 19: 93: 11: 48 - MAC address of the DHCP relay device | |||
Option length | 18 | Dec | ||||
Option value | 01:06 00: 04: 00: 01: 00: 04 02:08 00: 06: c8: be: 19: 93: 11: 48 | Hex | ||||
End of package | 255 | Dec | 1 | 255 symbolizes the end of the package |
DHCPOFFER
As soon as the server receives the DHCPDISCOVER packet and if it sees that it can offer the client something from the requested, then it forms a response for it - DHCPOFFER. The answer is sent to the port "from where it came", Broadcast, because at this moment, the client does not yet have an IP address, therefore, he can receive a packet only if it is sent broadcast. The client recognizes that it is a packet for him by MAC at his address inside the packet, as well as the transaction number that he generates at the time of creation of the first packet.
DHCPOFFER packet structure table
Package Position | Name of value (common) | Example | Representation | Byte | Explanation |
1 | Boot request | 1 | Hex | 1 | Type of message. 1 - request from client to server, 2 - response from server to client |
2 | Hardware type | 1 | Hex | 1 | Type of hardware address, in this protocol 1 - MAC |
3 | Hardware adrees length | 6 | Hex | 1 | Device MAC Address Length |
4 | Hops | 1 | Hex | 1 | Number of intermediate routes |
5 | Transaction id | 23: cf: de: 1d | Hex | 4 | Unique Transaction Identifier. Generated by the client at the beginning of the request operation |
7 | Second elapsed | 0 | Hex | 4 | Time in seconds since the beginning of the process of obtaining the address |
9 | Bootp flags | 0 | Hex | 2 | Some flags that can be set as an indication of protocol parameters. In this case, 0 means the Unicast request type |
eleven | Client IP address | 0.0.0.0 | Line | 4 | Client IP address (if any) |
fifteen | Your client IP address | 172.16.134.61 | Line | 4 | IP address proposed by the server (if any) |
19 | Next server IP address | 0.0.0.0 | Line | 4 | Server IP address (if known) |
23 | Relay agent IP address | 172.16.114.41 | Line | 4 | Relay agent IP address (for example, a switch) |
27 | Client MAC address | 14: d6: 4d: a7: c9: 55 | Hex | 6 | MAC address of the sender of the packet (client) |
31 | Client hardware address padding | Hex | 10 | Reserved place. Usually zeros | |
41 | Server host name | Line | 64 | The name of the DHCP server. Usually not transmitted | |
105 | Boot file name | Line | 128 | The file name on the server used by diskless stations during boot | |
235 | Magic cookie | 63: 82: 53: 63 | Hex | 4 | The "magic" number, by which incl. you can determine that this packet belongs to the DHCP protocol |
DHCP options. Can go in any order | |||||
236 | Option number | 53 | Dec | 1 | Option 53 specifying the type of DHCP 2 packet - DHCPOFFER |
Option length | 1 | Dec | 1 | ||
Option value | 2 | Dec | 1 | ||
Option number | 1 | Dec | 1 | Option offering DHCP client network mask | |
Option length | 4 | Dec | 1 | ||
Option value | 255.255.224.0 | Line | 4 | ||
Option number | 3 | Dec | 1 | Option offering DHCP client default gateway | |
Option length | 4 | Dec | 1 | ||
Option value | 172.16.12.1 | Line | 4 | ||
Option number | 6 | Dec | 1 | Option offering DHCP to the DNS client | |
Option length | 4 | Dec | 1 | ||
Option value | 8.8.8.8 | Line | 4 | ||
Option number | 51 | Dec | 1 | The lifetime of the issued network parameters in seconds, after which the DHCP client must request them again | |
Option length | 4 | Dec | 1 | ||
Option value | 86400 | Dec | 4 | ||
Option number | 82 | Dec | 1 | Option 82, repeats what came in DHCPDISCOVER | |
Option length | 18 | Dec | 1 | ||
Option value | 01: 08: 00: 06: 00 01: 01: 00: 00: 01 02: 06: 00: 03: 0f 26: 4d: ec | Dec | 18 | ||
End of package | 255 | Dec | 1 | 255 symbolizes the end of the package |
DHCPREQUEST
After the client receives DHCPOFFER, it forms a packet with a request for network parameters not to all DHCP servers in the network, but only to one specific one, whose DHCPOFFER proposal it most “liked”. The criteria for “like” can be different and depend on the implementation of the DHCP client. The recipient of the request is indicated using the MAC address of the DHCP server. Also, the DHCPREQUEST packet can be sent by the client without forming DHCPDISCOVER earlier, if the IP address of the server was previously received earlier.
DHCPREQUEST packet structure table
Package Position | Name of value (common) | Example | Representation | Byte | Explanation |
1 | Boot request | 1 | Hex | 1 | Type of message. 1 - request from client to server, 2 - response from server to client |
2 | Hardware type | 1 | Hex | 1 | Type of hardware address, in this protocol 1 - MAC |
3 | Hardware adrees length | 6 | Hex | 1 | Device MAC Address Length |
4 | Hops | 1 | Hex | 1 | Number of intermediate routes |
5 | Transaction id | 23: cf: de: 1d | Hex | 4 | Unique Transaction Identifier. Generated by the client at the beginning of the request operation |
7 | Second elapsed | 0 | Hex | 4 | Time in seconds since the beginning of the process of obtaining the address |
9 | Bootp flags | 8000 | Hex | 2 | Some flags that can be set as an indication of protocol parameters. In this case, “Broadcast” |
eleven | Client IP address | 0.0.0.0 | Line | 4 | Client IP address (if any) |
fifteen | Your client IP address | 172.16.134.61 | Line | 4 | IP address proposed by the server (if any) |
19 | Next server IP address | 0.0.0.0 | Line | 4 | Server IP address (if known) |
23 | Relay agent IP address | 172.16.114.41 | Line | 4 | Relay agent IP address (for example, a switch) |
27 | Client MAC address | 14: d6: 4d: a7: c9: 55 | Hex | 6 | MAC address of the sender of the packet (client) |
31 | Client hardware address padding | Hex | 10 | Reserved place. Usually zeros | |
41 | Server host name | Line | 64 | The name of the DHCP server. Usually not transmitted | |
105 | Boot file name | Line | 128 | The file name on the server used by diskless stations during boot | |
235 | Magic cookie | 63: 82: 53: 63 | Hex | 4 | The "magic" number, by which incl. you can determine that this packet belongs to the DHCP protocol |
DHCP options. Can go in any order | |||||
236 | Option number | 53 | Dec | 3 | Option 53 specifying the type of DHCP 3 packet - DHCPREQUEST |
Option length | 1 | Dec | 1 | ||
Option value | 3 | Dec | 1 | ||
Option number | 61 | Dec | 1 | Client ID: 01 (for Ehernet) + MAC address of the client | |
Option length | 7 | Dec | 1 | ||
Option value | 01: 2c: ab: 25: ff: 72: a6 | Hex | 7 | ||
Option number | 60 | Dec | "Vendor class identifier." In my case, the version of the DHCP client is reported. Perhaps other devices return something else. Windows for example reports MSFT 5.0 | ||
Option length | eleven | Dec | |||
Option value | udhcp 0.9.8 | Line | |||
Option number | 55 | 1 | Запрашиваемые клиентом сетевые параметры. Состав может быть различным 01 — Маска сети 03 — Шлюз 06 — DNS oc — Имя хоста 0f — имя домена сети 1c — адрес широковещательного запроса (бродкаста) 42 — имя сервера TFTP 79 — Classless Static Route | ||
Длина опции | 8 | 1 | |||
Значение опции | 01:03:06:0c:0f:1c:42:79 | 8 | |||
Номер опции | 82 | Dec | 1 | Опция 82, повторяет то что пришло в DHCPDISCOVER | |
Длина опции | 18 | Dec | 1 | ||
Значение опции | 01:08:00:06:00 01:01:00:00:01 02:06:00:03:0f 26:4d:ec | Dec | 18 | ||
Окончание пакета | 255 | Dec | 1 | 255 символизирует окончание пакета |
DHCPACK
As a confirmation of the fact that “yes, it’s your IP address, and I won’t give it to anyone else” from the DHCP server, there is a packet in DHCPACK format from the server to the client. It is the same as the rest of the packets sent broadcast. Although, in the below code of the DHCP server implemented in Python, just in case, I duplicate any broadcast request by sending a packet to a specific client IP, if it is already known. Moreover, the DHCP server does not care at all whether the DHCPACK packet has reached the client. If the client does not receive DHCPACK, then after a while it simply repeats DHCPREQUEST
DHCPACK Packet Structure Table
Package Position | Name of value (common) | Example | Representation | Byte | Explanation |
1 | Boot request | 2 | Hex | 1 | Type of message. 1 - request from client to server, 2 - response from server to client |
2 | Hardware type | 1 | Hex | 1 | Type of hardware address, in this protocol 1 - MAC |
3 | Hardware adrees length | 6 | Hex | 1 | Длина MAC адреса устройства |
4 | Hops | 1 | Hex | 1 | Количество промежуточных маршрутов |
5 | Transaction ID | 23:cf:de:1d | Hex | 4 | Уникальный идентификатор транзакции. Генерирует клиент в начале операции запроса |
7 | Second elapsed | 0 | Hex | 4 | Время в секундах с начала процесса получения адреса |
9 | Bootp flags | 8000 | Hex | 2 | Некие флаги, которые могут устанавливаться, в качестве указания параметров протокол. В данном случае выставлено «бродкаст» |
11 | Client IP address | 0.0.0.0 | Строка | 4 | IP адрес клиента (если есть) |
15 | Your client IP address | 172.16.134.61 | Строка | 4 | IP адрес предложенный сервером (если есть) |
19 | Next server IP address | 0.0.0.0 | Строка | 4 | IP адрес сервера (если известен) |
23 | Relay agent IP address | 172.16.114.41 | Строка | 4 | IP адрес агента ретрансляции (например свича) |
27 | Client MAC address | 14:d6:4d:a7:c9:55 | Hex | 6 | MAC адрес отправителя пакета (клиента) |
31 | Client hardware address padding | Hex | 10 | Зарезервированное место. Обычно забито нулями | |
41 | Server host name | Строка | 64 | Имя сервера DHCP. Обычно не передается | |
105 | Boot file name | Строка | 128 | Имя файла на сервере, используемое бездисковыми станциями при загрузке | |
235 | Magic cookie | 63:82:53:63 | Hex | 4 | «Магическое» число, по которому в т.ч. можно определить, что этот пакет — принадлежит протоколу DHCP |
Опции DHCP. Могут идти в любом порядке | |||||
236 | Номер опции | 53 | Dec | 3 | Опция 53, определяющая тип пакета DHCP 5 — DHCPACK |
Длина опции | 1 | Dec | 1 | ||
Значение опции | 5 | Dec | 1 | ||
Номер опции | 1 | Dec | 1 | Опция предлагающая DHCP клиенту маску сети | |
Длина опции | 4 | Dec | 1 | ||
Значение опции | 255.255.224.0 | Строка | 4 | ||
Номер опции | 3 | Dec | 1 | Опция предлагающая DHCP клиенту шлюз по умолчанию | |
Длина опции | 4 | Dec | 1 | ||
Значение опции | 172.16.12.1 | Строка | 4 | ||
Номер опции | 6 | Dec | 1 | Опция предлагающая DHCP клиенту DNS | |
Длина опции | 4 | Dec | 1 | ||
Значение опции | 8.8.8.8 | Строка | 4 | ||
Номер опции | 51 | Dec | 1 | Время жизни выданных сетевых параметров в секундах, через которое DHCP клиент должен запросить их снова | |
Длина опции | 4 | Dec | 1 | ||
Значение опции | 86400 | Dec | 4 | ||
Номер опции | 82 | Dec | 1 | Опция 82, повторяет то что пришло в DHCPDISCOVER | |
Длина опции | 18 | Dec | 1 | ||
Значение опции | 01:08:00:06:00 01:01:00:00:01 02:06:00:03:0f 26:4d:ec | Dec | 18 | ||
Окончание пакета | 255 | Dec | 1 | 255 symbolizes the end of the package |
Installation
Installation actually consists of installing the python modules necessary for the job. It is assumed that MySQL is already installed and configured.
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
We create the MySQL database, fill in the dump pydhcp.sql, configure the configuration file.
Configuration
All server settings are in the xml file format. Reference file:
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 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')
Now in more detail by tags:
The dhcpserver section describes the basic settings for starting the server, namely:
- host - which ip address the server is listening on port 67
- broadcast - which ip is a Broadcast for DHCPOFFER and DHCPACK
- DHCPServer - what is the ip of the DHCP server
- LeaseTime lease time of the issued ip address
- ThreadLimit - how many threads are simultaneously running to process incoming UDP packets on port 67. It is assumed that this will help on highly loaded projects;)
- defaultMask, defaultRouter, defaultDNS - what is offered to the subscriber by default if IP is found in the database, but no additional parameters are specified for him
The mysql section:
host, username, password, basename - everything speaks for itself. An exemplary database structure is available on GitHub.
Query section: This section describes the queries for obtaining OFFER / ACK:
- offer_count - the number of lines with requests that return a result of the form ip, mask, router, dns
- offer_n is the query string. If the return is empty, then the following offer request is executed
- history_sql - writing request, for example, to the "authorization history" by subscriber
Any variables from the options section or options from the DHCP protocol can participate in requests.
Section options. Here it is already more interesting. Here we can create variables that we can use later in the query section.
For instance:
option_82_hex:sw_port1:20:22
, this line is the command to take the whole line that came in the DHCP request of option 82, in hex format, in the range from 20 to 22 bytes, inclusive and put it in the new variable sw_port1 (the switch port where the request came from)
option_82_hex:sw_mac:26:40
, we define the sw_mac variable, taking hex from the 26:40 range.
You can see all the possible options that can be used in requests by starting the server with the -d switch. We will see something like this log:
- the DHCPINFORM packet came on port 67, from 0025224ad764, b '\ x91 \ xa5 \ xe0 \ xa3 \ xa5 \ xa9- \ x8f \ x8a', ('172.30.114.25', 68) {'ClientMacAddress': '0025224ad764', 'ClientMacAddressByte': b'\x00%"J\xd7d', 'HType': 'Ethernet', 'HostName': b'\x91\xa5\xe0\xa3\xa5\xa9-\x8f\x8a', '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'\x00\x00', 'giaddr': '172.30.114.25', 'gpoz': 308, 'hlen': 6, 'hops': 1, 'htype': 'MAC', 'magic_cookie': b'c\x82Sc', 'op': 'DHCPINFORM', 'option12': 12, 'option53': 53, 'option55': 55, 'option60': 60, 'option61': 61, 'option82': 82, 'option_82_byte': b'\x12\x01\x06\x00\x04\x00\x01\x00\x06\x02\x08\x00' b'\x06\x00\x1eX\x9e\xb2\xad', 'option_82_hex': '12010600040001000602080006001e589eb2ad', 'option_82_len': 18, 'option_82_str': "b'\\x12\\x01\\x06\\x00\\x04\\x00\\x01\\x00\\x06\\x02\\x08\\x00\\x06\\x00\\x1eX\\x9e\\xb2\\xad'", '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'}
Accordingly, we can wrap any variable in {} and it will be used in the SQL query.
Let us capture for history that the client received the IP address:


Server start
./pydhcpdb.py -d -c config.xml
- d DEBUG console output mode
- c <file_name> configuration file
Debriefing
And now more on the implementation of the server in Python. It is a pain. Python was studied on the fly. Many moments are made in the style of: “wow, I somehow did what works.” Not optimized at all, and left in this form mainly due to the small experience of development in python. I will dwell on the most interesting moments of the server implementation in the "code".
XML configuration file parser
The standard Python module xml.dom is used. It seems to be simple, but during the implementation there was a noticeable lack of sensible documentation and examples on the network using this module.
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
Oddly enough, multithreading in Python is implemented very clearly and simply.
def PacketWork (data, addr): ... # implementation of the analysis of the received package, and the answer to it ... while True: data, addr = udp_socket.recvfrom (1024) # wait for UDP packet thread = threading.Thread (target = PacketWork, args = (data, addr,)). start () # as it came - run the PacketWork function defined earlier in the background with parameters while threading.active_count ()> gconfig ["dhcp_ThreadLimit"]: time.sleep (1) # if the number of already running threads is greater than in the settings, wait until they become less
Receive / send DHCP packet
In order to intercept UDP packets coming through the network card, you need to "raise" the socket:
udp_socket = socket.socket (socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) udp_socket.bind ((gconfig ["dhcp_host"], 67))
where the flags are:
- AF_INET - means that the address format will be IP: port. Maybe AF_UNIX - where the address is given by the file name.
- SOCK_DGRAM - means that we are accepting not a “raw packet”, but already passed through a firewall, and with partially cut off packet. Those. we get only the UDP packet without the “physical” component of the UDP packet wrapper. If you use the SOCK_RAW flag, you will still need to parse this “wrapper”.
Sending a packet can be like a Broadcast:
udp_socket.setsockopt (socket.SOL_SOCKET, socket.SO_BROADCAST, 1) # switch the socket to Broadcast mode rz = udp_socket.sendto (packetack, (gconfig ["broadcast"], 68))
, and to the address, "where did the package come from":
udp_socket.setsockopt (socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # switch the socket to the "many listeners" mode rz = udp_socket.sendto (packetack, addr)
where SOL_SOCKET means “protocol level” for setting options
,, SO_BROADCAST option that the package is a Broadcast helmet
, SO_REUSEADDR option switches the socket to the “many listeners” mode. In theory, it is unnecessary in this case, but on one of the FreeBSD servers that I tested on, the code did not work without this option.
DHCP packet parsing
This is where I really liked Python. It turns out of the "box" it allows you to quite easily deal with the bytecode. Allowing it to be very simple to translate to decimal values, strings and hex - i.e. what we actually need to understand the structure of the package. So for example, you can get a range of bytes in HEX and just bytes:
res ["xidhex"] = data [4: 8] .hex () res ["xidbyte"] = data [4: 8]
, pack bytes into a structure:
res ["flags"] = pack ('BB', data [10], data [11])
Get IP from structure:
res ["ciaddr"] = socket.inet_ntoa (pack ('BBBB', data [12], data [13], data [14], data [15]));
And vice versa:
res = res + socket.inet_pton (socket.AF_INET, gconfig ["dhcp_Server"])
That's all ;)