Servidor DHCP+Mysql em Python

Servidor DHCP+Mysql em Python

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 DB2DHCP (meu fork), original aqui, 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 "explorar ao vivo".

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:

  1. 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.
  2. 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.
  3. 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.
  4. 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.

Servidor DHCP+Mysql em Python

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:

Servidor DHCP+Mysql em Python

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 GitHub

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:

Servidor DHCP+Mysql em Python

Servidor DHCP+Mysql em Python

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 diminua

Recepçã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

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster