
O objetivo deste projeto foi:
- Estudo do protocolo DHCP em uma rede IPv4
- Estudo de Python (um pouco mais do que do zero 😉 )
- substituição do servidor (meu fork), original , que se torna cada vez mais difícil de compilar para um novo sistema operacional. E não gosto de que o binário não possa ser "mudado imediatamente"
- obtenção de um servidor DHCP funcional com a capacidade de selecionar o endereço IP do assinante pelo MAC do assinante ou pela combinação de MAC do switch+porta (Opção 82)
- escrever mais um projeto (Oh! Esta é a minha atividade favorita)
- receber críticas sobre minha inabilidade no HabrHabr (ou melhor, um convite) 😉
Resultado: funciona 😉 Testado em sistemas operacionais FreeBSD e Ubuntu. Teoricamente, o código pode ser solicitado para funcionar em qualquer sistema operacional, pois não parecem haver ligações específicas no código.
Cuidado! A seguir tem muito.
Link para o repositório para os interessados .
O processo de instalação, configuração e uso do resultado do "estudo da matéria" está muito abaixo, e a seguir um pouco de teoria sobre o protocolo DHCP. Para mim. E para a história 😉
Um pouco de teoria
O que é DHCP
É um protocolo de rede que permite que um dispositivo descubra seu endereço IP (e outros parâmetros como gateway, DNS e afins) de um servidor DHCP. A troca de pacotes ocorre através do protocolo UDP. O princípio geral de operação do dispositivo ao solicitar os parâmetros da rede é o seguinte:
- O dispositivo (cliente) envia um pedido UDP de difusão (DHCPDISCOVER) para toda a rede com a solicitação "bem, alguém, me dê um endereço IP". Geralmente (mas não sempre), o pedido é feito da porta 68 (origem) e o destino é a porta 67 (destino). Alguns dispositivos também enviam pacotes pela porta 67. Dentro do pacote DHCPDISCOVER está incluído o endereço MAC do dispositivo cliente.
- Todos os servidores DHCP na rede (podendo haver vários) formam uma proposta DHCPOFFER com as configurações de rede para o dispositivo que enviou o DHCPDISCOVER e também a enviam de forma difusa pela rede. A identificação de para quem este pacote é destinado é feita pelo endereço MAC do cliente, fornecido anteriormente na solicitação DHCPDISCOVER.
- O cliente recebe pacotes com propostas de configurações de rede, escolhe a mais atraente (os critérios podem variar, inclusive pelo tempo de entrega do pacote e pelo número de rotas intermediárias) e faz um 'pedido oficial' DHCPREQUEST com as configurações de rede ao servidor DHCP que mais lhe agradou. Nesse caso, o pacote já vai para um servidor DHCP específico.
- O servidor que recebeu o DHCPREQUEST envia um pacote no formato DHCPACK, que mais uma vez lista as configurações de rede destinadas a esse cliente.

Além disso, existem pacotes DHCPINFORM que são enviados pelo cliente e têm como objetivo informar o servidor DHCP que 'o cliente está ativo' e utilizando as configurações de rede fornecidas. Nesta implementação do servidor, esses pacotes são ignorados.
Formato dos pacotes
De modo geral, o quadro de pacotes Ethernet tem uma aparência semelhante a esta:
![]()
Neste caso, vamos considerar apenas os dados contidos no pacote UDP, sem os cabeçalhos dos protocolos das camadas OSI, especificamente a estrutura do DHCP:
DHCPDISCOVER
Assim, o processo de obtenção de um endereço IP para um dispositivo começa com o cliente DHCP enviando uma solicitação de broadcast da porta 68 para 255.255.255.255:67. Neste pacote, o cliente inclui seu endereço MAC e também informa ao que deseja obter do servidor DHCP. A estrutura do pacote é descrita na tabela abaixo.
Tabela da estrutura do pacote DHCPDISCOVER
Posição no pacote
Nome do valor
Exemplo
Visualização
Byte
Explicação
1
Boot Request
1
Hex
1
Tipo de mensagem. 1 — solicitação do cliente ao servidor, 2 — resposta do servidor ao cliente
2
Hardware type
1
Hex
1
Tipo de endereço de hardware, neste protocolo 1 — MAC
3
Hardware address length
6
Hex
1
Comprimento do endereço MAC do dispositivo
4
Hops
1
Hex
1
Número de rotas intermediárias
5
Transaction ID
23:cf:de:1d
Hex
4
Identificador exclusivo da transação. Gerado pelo cliente no início da operação de solicitação
7
Second elapsed
0
Hex
4
Tempo em segundos desde o início do processo de obtenção do endereço
9
Bootp flags
0
Hex
2
Alguns flags que podem ser configurados como indicação de parâmetros do protocolo
11
Client IP address
0.0.0.0
String
4
Endereço IP do cliente (se houver)
15
Seu endereço IP do cliente
0.0.0.0
String
4
Endereço IP sugerido pelo servidor (se houver)
19
Endereço IP do próximo servidor
0.0.0.0
String
4
Endereço IP do servidor (se conhecido)
23
Endereço IP do agente de retransmissão
172.16.114.41
String
4
Endereço IP do agente de retransmissão (por exemplo, um switch)
27
Endereço MAC do cliente
14:d6:4d:a7:c9:55
Hex
6
Endereço MAC do remetente do pacote (cliente)
31
Preenchimento do endereço de hardware do cliente
Hex
10
Espaço reservado. Geralmente preenchido com zeros
41
Nome do host do servidor
String
64
Nome do servidor DHCP. Geralmente não é transmitido
105
Nome do arquivo de inicialização
String
128
Nome do arquivo no servidor usado por estações sem disco durante o carregamento
235
Cookie mágico
63:82:53:63
Hex
4
Número 'mágico' que pode ser utilizado para determinar que este pacote pertence ao protocolo DHCP
Opções DHCP. Podem vir em qualquer ordem
236
Número da opção
53
Dec
1
Opção 53, que define o tipo de pacote DHCP
1 — DHCPDISCOVER
3 — DHCPREQUEST
2 — DHCPOFFER
5 — DHCPACK
8 — DHCPINFORM
Comprimento da opção
1
Dec
1
Valor da opção
1
Dec
1
Número da opção
50
Dec
1
Qual IP o cliente deseja receber
Comprimento da opção
4
Dec
1
Valor da opção
172.16.134.61
String
4
Número da opção
55
1
Parâmetros de rede solicitados pelo cliente. A composição pode variar
01 — Máscara de rede
03 — Gateway
06 — DNS
oc — Nome do host
0f — nome do domínio da rede
1c — endereço de broadcast
42 — nome do servidor TFTP
79 — Rota Estática Sem Classe
Comprimento da opção
8
1
Valor da opção
01:03:06:0c:0f:1c:42:79
8
Número da opção
82
Dec
Opção 82, que transmite o endereço MAC do dispositivo retransmissor e alguns valores adicionais.
Na maioria das vezes — a porta do switch na qual o cliente final DHCP opera. Nesta opção, parâmetros adicionais estão 'embarcados'. O primeiro byte é o número da 'subopção', o segundo é seu comprimento, e a seguir seu valor.
Neste caso, na opção 82, estão embutidas subopções:
Circuito do Agente ID = 00:04:00:01:00:04, onde os últimos dois bytes correspondem à porta do cliente DHCP de onde a solicitação foi enviada
ID Remoto do Agente = 00:06:c8:be:19:93:11:48 — endereço MAC do dispositivo retransmissor DHCP
Comprimento da opção
18
Dec
Valor da opção
01:06
00:04:00:01:00:04
02:08
00:06:c8:be:19:93:11:48
Hex
Fim do pacote
255
Dec
1
255 simboliza o fim do pacote
DHCPOFFER
Assim que o servidor recebe o pacote DHCPDISCOVER e verifica que pode oferecer algo ao cliente, ele forma uma resposta — DHCPDISCOVER. A resposta é enviada para a porta 'de onde veio', em broadcast, pois, neste momento, o cliente ainda não tem um endereço IP, portanto, ele só pode receber o pacote se for enviado de forma ampla. O cliente reconhece que este pacote é para ele pelo seu endereço MAC dentro do pacote, assim como pelo número da transação gerado no momento da criação do primeiro pacote.
Tabela de estrutura do pacote DHCPOFFER
Posição no pacote
Nome do valor (comum)
Exemplo
Visualização
Byte
Explicação
1
Boot Request
1
Hex
1
Tipo de mensagem. 1 — solicitação do cliente ao servidor, 2 — resposta do servidor ao cliente
2
Hardware type
1
Hex
1
Tipo de endereço de hardware, neste protocolo 1 — MAC
3
Hardware address length
6
Hex
1
Comprimento do endereço MAC do dispositivo
4
Hops
1
Hex
1
Número de rotas intermediárias
5
Transaction ID
23:cf:de:1d
Hex
4
Identificador exclusivo da transação. Gerado pelo cliente no início da operação de solicitação
7
Second elapsed
0
Hex
4
Tempo em segundos desde o início do processo de obtenção do endereço
9
Bootp flags
0
Hex
2
Alguns flags que podem ser definidos como indicação de parâmetros do protocolo. Neste caso, 0 — indica um tipo de solicitação Unicast
11
Client IP address
0.0.0.0
String
4
Endereço IP do cliente (se houver)
15
Seu endereço IP do cliente
172.16.134.61
String
4
Endereço IP sugerido pelo servidor (se houver)
19
Endereço IP do próximo servidor
0.0.0.0
String
4
Endereço IP do servidor (se conhecido)
23
Endereço IP do agente de retransmissão
172.16.114.41
String
4
Endereço IP do agente de retransmissão (por exemplo, um switch)
27
Endereço MAC do cliente
14:d6:4d:a7:c9:55
Hex
6
Endereço MAC do remetente do pacote (cliente)
31
Preenchimento do endereço de hardware do cliente
Hex
10
Espaço reservado. Geralmente preenchido com zeros
41
Nome do host do servidor
String
64
Nome do servidor DHCP. Geralmente não é transmitido
105
Nome do arquivo de inicialização
String
128
Nome do arquivo no servidor usado por estações sem disco durante o carregamento
235
Cookie mágico
63:82:53:63
Hex
4
Número 'mágico' que pode ser utilizado para determinar que este pacote pertence ao protocolo DHCP
Opções DHCP. Podem vir em qualquer ordem
236
Número da opção
53
Dec
1
Opção 53, que define o tipo de pacote DHCP 2 — DHCPOFFER
Comprimento da opção
1
Dec
1
Valor da opção
2
Dec
1
Número da opção
1
Dec
1
Opção que oferece ao cliente DHCP uma máscara de rede
Comprimento da opção
4
Dec
1
Valor da opção
255.255.224.0
String
4
Número da opção
3
Dec
1
Opção que oferece ao cliente DHCP um gateway padrão
Comprimento da opção
4
Dec
1
Valor da opção
172.16.12.1
String
4
Número da opção
6
Dec
1
Opção que oferece ao cliente DHCP um DNS
Comprimento da opção
4
Dec
1
Valor da opção
8.8.8.8
String
4
Número da opção
51
Dec
1
Tempo de vida dos parâmetros de rede emitidos em segundos, após o qual o cliente DHCP deve solicitação-los novamente
Comprimento da opção
4
Dec
1
Valor da opção
86400
Dec
4
Número da opção
82
Dec
1
Opção 82, repete o que veio no DHCPDISCOVER
Comprimento da opção
18
Dec
1
Valor da opção
01:08:00:06:00
01:01:00:00:01
02:06:00:03:0f
26:4d:ec
Dec
18
Fim do pacote
255
Dec
1
255 simboliza o fim do pacote
DHCPREQUEST
Depois que o cliente recebe o DHCPOFFER, ele forma um pacote com a solicitação de parâmetros de rede não para todos os servidores DHCP na rede, mas apenas para um específico, cujo DHCPOFFER ele mais "apreciou". Os critérios de "aprecio" podem ser diversos e dependem da implementação do cliente DHCP. O destinatário da solicitação é especificado pelo endereço MAC do servidor DHCP. Além disso, o pacote DHCPREQUEST pode ser enviado pelo cliente mesmo sem a formação anterior do DHCPDISCOVER, se o endereço IP já tiver sido obtido anteriormente do servidor.
Tabela da estrutura do pacote DHCPREQUEST
Posição no pacote
Nome do valor (comum)
Exemplo
Visualização
Byte
Explicação
1
Boot Request
1
Hex
1
Tipo de mensagem. 1 — solicitação do cliente ao servidor, 2 — resposta do servidor ao cliente
2
Hardware type
1
Hex
1
Tipo de endereço de hardware, neste protocolo 1 — MAC
3
Hardware address length
6
Hex
1
Comprimento do endereço MAC do dispositivo
4
Hops
1
Hex
1
Número de rotas intermediárias
5
Transaction ID
23:cf:de:1d
Hex
4
Identificador exclusivo da transação. Gerado pelo cliente no início da operação de solicitação
7
Second elapsed
0
Hex
4
Tempo em segundos desde o início do processo de obtenção do endereço
9
Bootp flags
8000
Hex
2
Alguns flags que podem ser definidos como indicação de parâmetros do protocolo. Neste caso, está definido como "broadcast"
11
Client IP address
0.0.0.0
String
4
Endereço IP do cliente (se houver)
15
Seu endereço IP do cliente
172.16.134.61
String
4
Endereço IP sugerido pelo servidor (se houver)
19
Endereço IP do próximo servidor
0.0.0.0
String
4
Endereço IP do servidor (se conhecido)
23
Endereço IP do agente de retransmissão
172.16.114.41
String
4
Endereço IP do agente de retransmissão (por exemplo, um switch)
27
Endereço MAC do cliente
14:d6:4d:a7:c9:55
Hex
6
Endereço MAC do remetente do pacote (cliente)
31
Preenchimento do endereço de hardware do cliente
Hex
10
Espaço reservado. Geralmente preenchido com zeros
41
Nome do host do servidor
String
64
Nome do servidor DHCP. Geralmente não é transmitido
105
Nome do arquivo de inicialização
String
128
Nome do arquivo no servidor usado por estações sem disco durante o carregamento
235
Cookie mágico
63:82:53:63
Hex
4
Número 'mágico' que pode ser utilizado para determinar que este pacote pertence ao protocolo DHCP
Opções DHCP. Podem vir em qualquer ordem
236
Número da opção
53
Dec
3
Opção 53, que define o tipo de pacote DHCP 3 - DHCPREQUEST
Comprimento da opção
1
Dec
1
Valor da opção
3
Dec
1
Número da opção
61
Dec
1
Identificador do cliente: 01 (para Ethernet) + endereço MAC do cliente
Comprimento da opção
7
Dec
1
Valor da opção
01:2c:ab:25:ff:72:a6
Hex
7
Número da opção
60
Dec
"Vendor class identifier". No meu caso, informa a versão do cliente DHCP. Outros dispositivos podem retornar algo diferente. O Windows, por exemplo, reporta MSFT 5.0
Comprimento da opção
11
Dec
Valor da opção
udhcp 0.9.8
String
Número da opção
55
1
Parâmetros de rede solicitados pelo cliente. A composição pode variar
01 — Máscara de rede
03 — Gateway
06 — DNS
oc — Nome do host
0f — nome do domínio da rede
1c — endereço de broadcast
42 — nome do servidor TFTP
79 — Rota Estática Sem Classe
Comprimento da opção
8
1
Valor da opção
01:03:06:0c:0f:1c:42:79
8
Número da opção
82
Dec
1
Opção 82, repete o que veio no DHCPDISCOVER
Comprimento da opção
18
Dec
1
Valor da opção
01:08:00:06:00
01:01:00:00:01
02:06:00:03:0f
26:4d:ec
Dec
18
Fim do pacote
255
Dec
1
255 simboliza o fim do pacote
DHCPACK
Como confirmação de que "sim, este é realmente o seu IP, e eu não o passarei para mais ninguém" do servidor DHCP, serve o pacote no formato DHCPACK do servidor para o cliente. Ele também, como os outros pacotes, é enviado de forma broadcast. No entanto, no código abaixo do servidor DHCP implementado em Python, eu caso precise, duplico qualquer solicitação broadcast, enviando o pacote para um IP específico do cliente, se já for conhecido. Além disso, o servidor DHCP não se preocupa se o pacote DHCPACK chegou ao cliente. Se o cliente não recebe o DHCPACK, após algum tempo ele simplesmente repete o DHCPREQUEST.
Tabela da estrutura do pacote DHCPACK
Posição no pacote
Nome do valor (comum)
Exemplo
Visualização
Byte
Explicação
1
Boot Request
2
Hex
1
Tipo de mensagem. 1 — solicitação do cliente ao servidor, 2 — resposta do servidor ao cliente
2
Hardware type
1
Hex
1
Tipo de endereço de hardware, neste protocolo 1 — MAC
3
Hardware address length
6
Hex
1
Comprimento do endereço MAC do dispositivo
4
Hops
1
Hex
1
Número de rotas intermediárias
5
Transaction ID
23:cf:de:1d
Hex
4
Identificador exclusivo da transação. Gerado pelo cliente no início da operação de solicitação
7
Second elapsed
0
Hex
4
Tempo em segundos desde o início do processo de obtenção do endereço
9
Bootp flags
8000
Hex
2
Alguns flags que podem ser definidos como indicação de parâmetros do protocolo. Neste caso, está definido como "broadcast"
11
Client IP address
0.0.0.0
String
4
Endereço IP do cliente (se houver)
15
Seu endereço IP do cliente
172.16.134.61
String
4
Endereço IP sugerido pelo servidor (se houver)
19
Endereço IP do próximo servidor
0.0.0.0
String
4
Endereço IP do servidor (se conhecido)
23
Endereço IP do agente de retransmissão
172.16.114.41
String
4
Endereço IP do agente de retransmissão (por exemplo, um switch)
27
Endereço MAC do cliente
14:d6:4d:a7:c9:55
Hex
6
Endereço MAC do remetente do pacote (cliente)
31
Preenchimento do endereço de hardware do cliente
Hex
10
Espaço reservado. Geralmente preenchido com zeros
41
Nome do host do servidor
String
64
Nome do servidor DHCP. Geralmente não é transmitido
105
Nome do arquivo de inicialização
String
128
Nome do arquivo no servidor usado por estações sem disco durante o carregamento
235
Cookie mágico
63:82:53:63
Hex
4
Número 'mágico' que pode ser utilizado para determinar que este pacote pertence ao protocolo DHCP
Opções DHCP. Podem vir em qualquer ordem
236
Número da opção
53
Dec
3
Opção 53, que define o tipo de pacote DHCP 5 - DHCPACK
Comprimento da opção
1
Dec
1
Valor da opção
5
Dec
1
Número da opção
1
Dec
1
Opção que oferece ao cliente DHCP uma máscara de rede
Comprimento da opção
4
Dec
1
Valor da opção
255.255.224.0
String
4
Número da opção
3
Dec
1
Opção que oferece ao cliente DHCP um gateway padrão
Comprimento da opção
4
Dec
1
Valor da opção
172.16.12.1
String
4
Número da opção
6
Dec
1
Opção que oferece ao cliente DHCP um DNS
Comprimento da opção
4
Dec
1
Valor da opção
8.8.8.8
String
4
Número da opção
51
Dec
1
Tempo de vida dos parâmetros de rede emitidos em segundos, após o qual o cliente DHCP deve solicitação-los novamente
Comprimento da opção
4
Dec
1
Valor da opção
86400
Dec
4
Número da opção
82
Dec
1
Opção 82, repete o que veio no DHCPDISCOVER
Comprimento da opção
18
Dec
1
Valor da opção
01:08:00:06:00
01:01:00:00:01
02:06:00:03:0f
26:4d:ec
Dec
18
Fim do pacote
255
Dec
1
255 simboliza o fim do pacote
Instalação
A instalação consiste basicamente na instalação dos módulos Python necessários para o funcionamento. Presume-se que o MySQL já esteja instalado e configurado.
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
Criamos um banco de dados MySQL, importamos o dump pydhcp.sql e configuramos o arquivo de configuração.
Configuração
Todas as configurações do servidor estão no arquivo no formato XML. Arquivo de referência:
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')Agora vamos entrar em detalhes sobre as tags:
A seção dhcpserver descreve as configurações principais para o funcionamento do servidor, a saber:
- host — qual endereço IP o servidor escuta na porta 67
- broadcast — qual IP é o broadcast para DHCPOFFER e DHCPACK
- DHCPServer — qual é o IP do servidor DHCP
- LeaseTime — tempo de locação do endereço IP concedido
- ThreadLimit — quantas threads podem ser executadas simultaneamente para o processamento de pacotes UDP recebidos na porta 67. Presumivelmente, isso ajudará em projetos de alta carga 😉
- defaultMask, defaultRouter, defaultDNS — o que é oferecido ao cliente por padrão, se o IP é encontrado na base, mas parâmetros adicionais não foram especificados
Seção mysql:
host, username, password, basename — tudo é autoexplicativo. A estrutura aproximada do banco de dados está disponível em
Seção query: aqui são descritas as consultas para obter OFFER/ACK:
- offer_count — número de linhas com consultas que retornam resultados no formato ip, mask, router, dns
- offer_n — string de consulta. Se o retorno for vazio, a próxima consulta offer é executada
- history_sql — consulta que escreve, por exemplo, no "histórico de autenticação" do cliente
Nas consultas podem participar quaisquer variáveis da seção options ou opções do protocolo DHCP.
Seção options. Aqui as coisas ficam mais interessantes. Podemos criar variáveis que podemos usar posteriormente na seção query.
Por exemplo:
option_82_hex:sw_port1:20:22, este comando deve capturar toda a string recebida na opção 82 do pedido DHCP, no formato hex, do byte 20 ao 22 inclusive, e colocá-la em uma nova variável sw_port1 (porta do switch de onde veio o pedido)
option_82_hex:sw_mac:26:40, definimos a variável sw_mac, capturando o hex do intervalo 26:40
Para ver todas as opções possíveis que podem ser usadas nas solicitações, você pode iniciar o servidor com a chave -d. Veremos um log aproximadamente assim:
--pacote DHCPINFORM recebido na porta 67, de 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'}Assim, podemos envolver qualquer variável em {} e ela será utilizada na consulta SQL.
Vamos registrar para a história qual IP o cliente recebeu:


Iniciar o servidor
.\/pydhcpdb.py -d -c config.xml
— d modo de saída no console DEBUG
— c <nome_arquivo> arquivo de configuração
Análise de desempenho
Agora, vamos discutir mais sobre a implementação do servidor em Python. É uma dor. O Python foi aprendido 'em movimento'. Muitos pontos foram feitos no estilo: 'nossa, como isso funciona'. Não está otimizado e foi deixado assim principalmente devido à pouca experiência em desenvolvimento em Python. Irei me concentrar nos pontos mais interessantes da implementação do servidor em 'código'.
Parser de arquivo de configuração XML
Utiliza o módulo padrão do Python xml.dom. Parece simples, mas na implementação senti falta de uma documentação e exemplos bons na rede usando este módulo.
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
Curiosamente, a multithreading em Python é implementada de forma bastante clara e simples.
def PacketWork(data, addr):
...
# implementação da análise do pacote recebido e resposta a ele
...
while True:
data, addr = udp_socket.recvfrom(1024) # aguarda pacote UDP
thread = threading.Thread(target=PacketWork, args=(data, addr,)).start() # ao receber, inicia em segundo plano a função PacketWork com os parâmetros definidos
while threading.active_count() > gconfig["dhcp_ThreadLimit"]:
time.sleep(1) # se o número de threads ativas exceder o limite configurado, espera até que diminuaRecepção/envio de pacotes DHCP
Para interceptar pacotes UDP que atravessam a interface de rede, é necessário "levantar" o socket:
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) udp_socket.bind((gconfig["dhcp_host"], 67))
, onde as bandeiras:
- AF_INET — significa que o formato do endereço será IP: porta. Também pode ser AF_UNIX — onde o endereço é especificado pelo nome de um arquivo.
- SOCK_DGRAM significa que recebemos não um 'pacote bruto', mas já filtrado pelo firewall, e com um pacote cortado parcialmente. Ou seja, recebemos apenas o pacote UDP sem a parte 'física' da camada de encapsulamento do pacote UDP. Se utilizarmos a flag SOCK_RAW, será necessário ainda analisar essa 'camada de encapsulamento'.
O envio do pacote pode ser tanto por broadcast:
udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) #mudamos o socket para modo de envio de broadcast
rz=udp_socket.sendto(packetack, (gconfig["broadcast"],68)), quanto para o endereço 'de onde veio o pacote':
udp_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #mudamos o socket para modo 'múltiplos ouvintes'
rz=udp_socket.sendto(packetack, addr), onde SOL_SOCKET significa 'nível de protocolo' para definir opções,
, SO_BROADCAST é a opção que informa que o pacote está sendo enviado 'por broadcast'
, SO_REUSEADDR é a opção que muda o socket para o modo 'múltiplos ouvintes'. A princípio, isso não é necessário neste caso, mas em um dos servidores FreeBSD onde teste realizado, sem essa opção o código não funcionava.
Análise do pacote DHCP
Aqui eu realmente gostei do Python. Veja, ele permite trabalhar de forma bastante simples com código em bytes. Permite que convertemos facilmente em valores decimais, strings e hexadecimais — ou seja, o que realmente precisamos para entender a estrutura do pacote. Por exemplo, podemos obter um intervalo de bytes em HEX e bytes simples:
res["xidhex"]=data[4:8].hex()
res["xidbyte"]=data[4:8], empacotar os bytes em uma estrutura:
res["flags"]=pack('BB',data[10],data[11])Obter o IP da estrutura:
res["ciaddr"]=socket.inet_ntoa(pack('BBBB',data[12],data[13],data[14],data[15]));E vice-versa:
res=res+socket.inet_pton(socket.AF_INET, gconfig["dhcp_Server"])
E é isso! 😉
Fonte: habr.com
