DHCP+Mysql сервер на Python

DHCP+Mysql сервер на Python

Целью данного проекта было:

  • Изучение протокола DHCP при работе в сети IPv4
  • Изучение Python (немножко более чем с нуля 😉 )
  • замена серверу DB2DHCP (мой форк), оригинал здесь, который собирать под новую ОС всё труднее и труднее. Да и не нравится, что бинарник, который нет возможности «поменять прям щас»
  • получение работоспособного сервера DHCP с возможностью выборки IP адреса абонента по mac абонента или связке mac свича+порт (Option 82)
  • написание очередного велосипеда (О! это моё любимое занятие)
  • получение люлей про свою косорукость на Хабрахабр (а лучше инвайта) 😉

Результат:  работает 😉 Опробовано на ОС FreeBSD и Ubuntu. Теоретически код можно попросить работать под любой ОС, т.к. специфических привязок в коде как будто нет.
Осторожно! Дальше много.

Ссылка на репозиторий для любителей «потрогать живьем».

Процесс установки, настройки и использования результата «изучения матчасти» много ниже, а  далее немножко теории по протоколу DHCP. Для себя. И для истории 😉

Немножко теории

Что такое DHCP

Это сетевой протокол который позволяет устройству узнать свой IP адрес (ну и другие параметры вроде шлюза, DNS и прочего), у сервера DHCP. Обмен пакетами идет по протоколу UDP. Общий принцип работы устройства при запросе параметров сети следующий:

  1. Устройство (клиент) рассылает широковещательный UDP запрос (DHCPDISCOVER) по всей сети с запросом «ну кто-нибудь, дайте мне IP адрес». Причем обычно (но не всегда) запрос происходит с 68 порта (источник), а назначение — 67 порт (назначение). Некоторые устройства отправляют пакеты и с 67 порта. Внутри пакета DHCPDISCOVER включен MAC адрес устройства клиента.
  2. Все сервера DHCP, находящиеся в сети (а их может быть несколько), формируют для устройства отправившего DHCPDISCOVER, предложение DHCPOFFER с сетевыми настройками, и так-же широковещательно его отсылает его по сети. Идентификация кому предназначен этот пакет идет по MAC адресу клиента, предоставленного ранее в запросе DHCPDISCOVER.
  3. Клиент принимает пакеты с предложениями сетевых настроек, выбирает наиболее привлекательный (критерии могут быть различными, например в т.ч. и по времени доставки пакета, количестве промежуточных маршрутов), и делает у понравившегося сервера DHCP «официальный запрос» DHCPREQUEST с сетевыми настройками. В этом случае пакет идет уже к конкретному серверу DHCP.
  4. Сервер, получивший DHCPREQUEST, отправляет пакет формата DHCPACK, в котором в очередной раз перечисляет сетевые настройки предназначенные для данного клиента

DHCP+Mysql сервер на Python

Кроме того, есть пакеты DHCPINFORM, которые ходят от клиента, и цель которых проинформировать DHCP сервер о том, что «клиент жив» и пользуется выданными сетевыми настройками. В реализации данного сервера эти пакеты игнорируются.

Формат пакетов

В целом фрейм пакета Ethernet выглядит примерно так:

DHCP+Mysql сервер на Python

В нашем случаем мы рассмотрим только данные непосредственно содержимого пакета UDP, без заголовков протоколов уровней OSI, а именно структуру DHCP:

DHCPDISCOVER

Итак, процесс получения IP адреса для устройства начинается с того, что клиент DHCP рассылает широковещательный запрос с порта 68 на 255.255.255.255:67. В этом пакете клиент включает свой MAC адрес, а так-же что именно он хочет получить от DHCP сервера. Структура пакета описана в виде таблицы ниже.

Таблица структуры пакета DHCPDISCOVER

Позиция в пакете
Название значения
Пример
Представление
Байт
Пояснение

1
Boot Request
1
Hex
1
Тип сообщения. 1 — запрос от клиента к серверу, 2 — ответ от сервера клиенту

2
Hardware type
1
Hex
1
Тип аппаратного адреса, в данном протоколе 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
0
Hex
2
Некие флаги, которые могут устанавливаться, в качестве указания параметров протокола

11
Client IP address
0.0.0.0
Строка
4
IP адрес клиента (если есть)

15
Your client IP address
0.0.0.0
Строка
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
1
Опция 53, определяющая тип пакета DHCP

1 — DHCPDISCOVER
3 — DHCPREQUEST
2 — DHCPOFFER
5 — DHCPACK
8 — DHCPINFORM

 
Длина опции
1
Dec
1

 
Значение опции
1
Dec
1

 
Номер опции
50
Dec
1
Какой IP адрес хочет получить клиент

 
Длина опции
4
Dec
1

 
Значение опции
172.16.134.61
Строка
4

 
Номер опции
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
 
Опция 82, в которой передается MAC адрес устройства — ретранслятора и какие-то дополнительные значения.

Чаще всего — порт свича на котором работает конечный клиент DHCPВ данной опции «вложены» дополнительные параметры.Первый байт — номер «подопции», второй её длина, далее её значение.

В данном случае в опции 82, вложены подопции:
Agent Circuit ID = 00:04:00:01:00:04, где последние два байта — порт клиента DHCP с которого пришел запрос

Agent Remote ID = 00:06:c8:be:19:93:11:48 — MAC адрес устройства ретранслятора DHCP

 
Длина опции
18
Dec
 

 
Значение опции
01:06
00:04:00:01:00:04
02:08
00:06:c8:be:19:93:11:48
Hex
 

 
Окончание пакета
255
Dec
1
255 символизирует окончание пакета

DHCPOFFER

Как только сервер получает пакет DHCPDISCOVER и если он видит, что может клиенту что-то предложить из запрошенного, то он формирует для него ответ — DHCPDISCOVER. Ответ высылается на порт «откуда пришел», бродкастом, т.к. в этот момент, у клиента еще нет IP адреса, следовательно пакет он может принять, только если он отослан широковещательно. Клиент распознает что это пакет для него по MAC своему адресу внутри пакета, а так-же номеру транзакции, который он генерирует в момент создания первого пакета.

Таблица структуры пакета DHCPOFFER

Позиция в пакете
Название значения (общепринятое)
Пример
Представление
Байт
Пояснение

1
Boot Request
1
Hex
1
Тип сообщения. 1 — запрос от клиента к серверу, 2 — ответ от сервера клиенту

2
Hardware type
1
Hex
1
Тип аппаратного адреса, в данном протоколе 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
0
Hex
2
Некие флаги, которые могут устанавливаться, в качестве указания параметров протокола. В данном случае, 0 — означает тип запроса Unicast

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
1
Опция 53, определяющая тип пакета DHCP 2 — DHCPOFFER

 
Длина опции
1
Dec
1

 
Значение опции
2
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 символизирует окончание пакета

DHCPREQUEST

После того, как клиент получит DHCPOFFER, он формирует пакет с запросом сетевых параметров уже не ко всем серверам DHCP в сети, а только к одному конкретному, предложение DHCPOFFER которого, ему наиболее «понравилось». Критерии «понравилось» могут быть различные и зависят от реализации DHCP клиента. Получатель запроса указывается при помощи MAC адреса сервера DHCP. Так-же пакет DHCPREQUEST может быть выслан клиентом и без формирования ранее DHCPDISCOVER, если IP адрес у сервера уже ранее когда-то был получен.

Таблица структуры пакета DHCPREQUEST

Позиция в пакете
Название значения (общепринятое)
Пример
Представление
Байт
Пояснение

1
Boot Request
1
Hex
1
Тип сообщения. 1 — запрос от клиента к серверу, 2 — ответ от сервера клиенту

2
Hardware type
1
Hex
1
Тип аппаратного адреса, в данном протоколе 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 3 — DHCPREQUEST

 
Длина опции
1
Dec
1

 
Значение опции
3
Dec
1

 
Номер опции
61
Dec
1
Идентификатор клиента: 01 (для Ehernet) + MAC адрес клиента

 
Длина опции
7
Dec
1

 
Значение опции
01:2c:ab:25:ff:72:a6
Hex
7

 
Номер опции
60
Dec
 
«Vendor class identifier». В моем случае сообает версию DHCP клиента. Возможно другие устройства, возвращают что-то другое. Windows например сообщает MSFT 5.0

 
Длина опции
11
Dec
 

 
Значение опции
udhcp 0.9.8
Строка
 

 
Номер опции
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

В качестве подтверждения того, что «да точно, это твой IP адрес, и больше я его никому не выдам» от DHCP сервера, служит пакет в формате DHCPACK от сервера клиенту. Он так-же как и остальные пакеты высылается широковещательно. Хотя, в ниже приведенном коде DHCP сервера реализованного на Python, я на всякий случай дублирую любой широковещательный запрос, отправкой пакета на конкретный IP клиента, если он уже известен. Причем DHCP сервер совершенно не волнует, дошел ли до клиента пакет DHCPACK. Если клиент не получает DHCPACK, то через некоторое время он просто повторяет DHCPREQUEST

Таблица структуры пакета DHCPACK

Позиция в пакете
Название значения (общепринятое)
Пример
Представление
Байт
Пояснение

1
Boot Request
2
Hex
1
Тип сообщения. 1 — запрос от клиента к серверу, 2 — ответ от сервера клиенту

2
Hardware type
1
Hex
1
Тип аппаратного адреса, в данном протоколе 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 символизирует окончание пакета

Установка

Установка фактически заключается в установке модулей python необходимых для работы. Предполагается что MySQL уже установлена и настроена.

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

Создаем БД MySQL, заливаем в неё дамп pydhcp.sql, настраиваем файл конфигурации.

Конфигурация

Все настройки сервера лежат в файле формата xml. Эталонный файл:

<?xml version="1.0" ?>
<config>
    <dhcpserver>
	<host>0.0.0.0</host>
        <broadcast>255.255.255.255</broadcast>
        <DHCPServer>192.168.0.71</DHCPServer>
	<LeaseTime>8600</LeaseTime>
	<ThreadLimit>1</ThreadLimit>
        <defaultMask>255.255.255.0</defaultMask>
        <defaultRouter>192.168.0.1</defaultRouter>
        <defaultDNS>8.8.8.8</defaultDNS>
    </dhcpserver>
    <mysql>
        <host>localhost</host>
	<username>test</username>
	<password>test</password>
	<basename>pydhcp</basename>
    </mysql>
    <options>
       <option>option_82_hex:sw_port1:20:22</option>       
       <option>option_82_hex:sw_port2:16:18</option>       
       <option>option_82_hex:sw_mac:26:40</option>
    </options>    
    <query>
        <offer_count>3</offer_count>
	<offer_1>select ip,mask,router,dns from users where upper(mac)=upper('{option_82_AgentRemoteId_hex}') and upper(port)=upper('{option_82_AgentCircuitId_port_hex}')</offer_1>
        <offer_2>select ip,mask,router,dns from users where upper(mac)=upper('{sw_mac}') and upper(port)=upper('{sw_port2}')</offer_2>
        <offer_3>select ip,mask,router,dns from users where upper(mac)=upper('{ClientMacAddress}')</offer_3>
	<history_sql>insert into history (id,dt,mac,ip,comment) values (null,now(),'{ClientMacAddress}','{RequestedIpAddress}','DHCPACK/INFORM')</history_sql>
    </query>
</config>

Теперь поподробнее по тегам:

Секция dhcpserver описывает основные настройки для запуска сервера, а именно:

  • host — какой ip адрес слушает сервер на порту 67
  • broadcast — какой ip является бродкастом для DHCPOFFER и DHCPACK
  • DHCPServer — какой ip у DHCP сервера
  • LeaseTime время аренды выданного ip адреса
  • ThreadLimit — сколько одновременно потоков запущено по обработке поступивших пакетов UDP на порту 67. Предполагается что поможет на высоконагруженных проектах 😉
  • defaultMask,defaultRouter,defaultDNS — то что предлагается абоненту по умолчанию, если IP в базе найден, но дополнительные параметры для него не указаны

Секция mysql:

host,username,password,basename  — всё говорит само за себя. Примерная структура базы данных выложена на GitHub

Секция query: здесь описываются запросы для получения OFFER/ACK:

  • offer_count — количество строк с запросами которые возвращают результат вида ip,mask,router,dns
  • offer_n — строка запроса. Если возврат — пусто, то выполняет следующий запрос offer
  • history_sql — запрос пишуший например в «историю авторизации» по абоненту

В запросах могут участвовать любые переменные из секции options или опции из протокола DHCP.

Секция options. Вот тут уже интереснее.  Тут мы можем создавать переменные которые можем использовать в дальнейшем в секции query.

Например:

option_82_hex:sw_port1:20:22

, эта строчка-команда взять всю строку пришедшую в DHCP запросе опции 82, в формате hex, в диапазоне с 20 по 22 байт фключительно и положить её в новую переменную sw_port1  (порт свича откуда пришел запрос)

option_82_hex:sw_mac:26:40

, опеределяем переменную sw_mac, взяв hex из диапазона 26:40

Увидеть все возможные опции которые можно использовать в запросах, можно при помощи запуска сервера с ключем -d. Увидим примерно такой лог:

--пришел пакет  DHCPINFORM  на 67 порт,от  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'}

Соответственно мы можем любую переменную обернуть в {} и она будет использована в SQL запросе.

Запечатлим для истории, что IP адрес клиент получил:

DHCP+Mysql сервер на Python

DHCP+Mysql сервер на Python

Запуск сервера

./pydhcpdb.py -d -c config.xml

— d режим вывода в консоль DEBUG
— c <имя_файла> конфигурационный файл

Разбор полетов

А теперь подробнее по реализации сервера на Python. Это боль. Python изучался «на лету». Многие моменты сделаны в стиле: «ухты, как-то сделал что работает». Совсем не оптимизированны, и оставлены в таком виде в основном  из-за малого опыта разработки на python. Остановлюсь на наиболее интересных моментах реализации сервера в «коде».

Парсер файла конфигурации XML

Используется стандартный модуль Python xml.dom. Вроде бы и просто, но при реализации ощутимо не хватало толковой документации и примеров в сети с использованием данного модуля.

    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)

Многопоточность

Как ни странно, многопоточность в Python реализована очень понятно и просто.

def PacketWork(data,addr): 
...
# реализация разбора пришедшего пакета, и ответа на него
...


while True:
    data, addr = udp_socket.recvfrom(1024) # ждем пакет UDP
    thread = threading.Thread(target=PacketWork, args=(data,addr,)).start()	# как пришел - запускаем в фоне определенную ранее функцию PacketWork с параметрами
    while threading.active_count() >gconfig["dhcp_ThreadLimit"]:
       time.sleep(1) # если число уже запущеных потоков больше чем в настройках, ждем пока их станет меньше

Прием/отправка пакета DHCP

Для того чтобы перехватить пакеты UDP идущие через сетевую карту, нужно «поднять» сокет:

udp_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,socket.IPPROTO_UDP)
udp_socket.bind((gconfig["dhcp_host"],67))

, где флаги:

  • AF_INET — означет что формат адреса будет IP: порт. Может быть еще AF_UNIX — где адрес задается именем файла.
  • SOCK_DGRAM — означает, что принимаем не «сырой пакет», а уже прошедший через файревол, и с обрезанным частично пакетом. Т.е. получаем только пакет UDP без «физической» составляющей обертки пакета UDP. Если использовать флаг SOCK_RAW, то необходимо будет еще парсить и это «обертку».

Отправка пакета может быть как бродкастом:

                    udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) #переключаем сокет в режим отправки бродкаста
                    rz=udp_socket.sendto(packetack, (gconfig["broadcast"],68))

, так и на адрес, «откуда пришел пакет»:

                        udp_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # переключаем сокет в режим "много слушаетелей"
                        rz=udp_socket.sendto(packetack, addr)

, где SOL_SOCKET означает «уровень протокола» для выставления опций,

, SO_BROADCAST опция что пакет шлем «бродкастом»

  ,SO_REUSEADDR опция переключающая сокет в режим «много слушателей». По идее она ненужна в данном случае, но на одном из серверов FreeBSD, на котором тестировал, без этой опции код не работал.

Разбор пакета DHCP

Вот тут мне действительно понравился Python. Оказывается из «коробки» он позволяет довольно вольно обходится с байт-кодом. Позволяя его очень просто переводить в десятичные значения, строки и hex — т.е. то что нам собственно и нужно, чтобы понять структуру пакета. Так например можно получить диапазон байт в HEX и просто байтах:

    res["xidhex"]=data[4:8].hex()
    res["xidbyte"]=data[4:8]

, упаковать байты в структуру:

res["flags"]=pack('BB',data[10],data[11])

Получить IP из структуры:

res["ciaddr"]=socket.inet_ntoa(pack('BBBB',data[12],data[13],data[14],data[15]));

И наоборот:

res=res+socket.inet_pton(socket.AF_INET, gconfig["dhcp_Server"])

На этом всё 😉

Источник: habr.com

Добавить комментарий