Criando um mini servidor ACS com Python

Criando um mini servidor ACS com Python

Hoje em dia o caminho padrão é instalar o GenieACS, apontar o CPE e seguir a vida. Funciona. O efeito colateral é que muita gente opera ACS o dia inteiro sem nunca ter visto a conversa crua na porta 7547: o Inform chegando, o SOAP de resposta, o POST vazio, o Connection Request.

Neste post a gente monta um ACS mínimo em Python — sem Mongo, sem UI, sem provision. Só o suficiente para o roteador bater, você ler o Inform em memória e (quando a rede deixar) disparar um Connection Request. Validei com um ZTE ZXHN H3601P de verdade. O código está no Diretório → Scripts.

Isso não substitui GenieACS em produção. Substitui a caixa-preta na cabeça do aluno.

Variáveis
preencha uma vez · clique no token no texto para voltar

URL no CPE: http://ACS_IP:7547/

Como a sessão CWMP acontece (na prática)

Esquece o desenho de slides por um minuto. O que o CPE faz é HTTP:

  1. O roteador abre TCP até o ACS na porta 7547 e manda um POST com SOAP: método Inform. Dentro vão serial, OUI, modelo, eventos (0 BOOTSTRAP, 2 PERIODIC, 6 CONNECTION REQUEST…) e uma lista de parâmetros (versão de software, ConnectionRequestURL, IP de WAN…).
  2. O ACS responde 200 com InformResponse (MaxEnvelopes=1).
  3. O CPE manda outro POST — muitas vezes vazio. É a vez do ACS falar: pode mandar GetParameterValues, SetParameterValues, etc., ou responder 204 e encerrar.
  4. Se o ACS quiser “acordar” o CPE fora do Inform periódico, faz HTTP GET no ConnectionRequestURL que veio no Inform (às vezes com Digest). O CPE, se alcançar, inicia outra sessão (evento 6 CONNECTION REQUEST).

GenieACS faz isso o tempo todo. A diferença é que aqui você cada passo no log.

Armadilha de lab: LAN × WAN

Alguns roteadores (no meu lab, o ZTE H3601P) não disparam TR-069 de verdade contra um ACS só na LAN. Você coloca http://192.168.1.x:7547/, salva, e… silêncio. O mesmo CPE, apontando para um ACS alcançável pela WAN (IP público / VPS), manda Inform na hora.

Não é “Python errado”. É comportamento do firmware + caminho de gerenciamento. Se o Inform não chega com ACS na LAN:

  • suba o ACS num host com IP que o CPE enxergue pela WAN (lab em VPS, ou port-forward bem feito);
  • libere a porta 7547/tcp no firewall;
  • no CPE, URL no formato http://ACS_IP:7547/ (barra no final ajuda em vários modelos).

Outro detalhe que apareceu no teste: o ConnectionRequestURL veio em faixa 100.64/10 (CGNAT do provedor). Inform WAN → ACS público funciona; Connection Request do ACS de volta para o CPE pode falhar sem rota. Para aula de Inform/SOAP, Inform periódico já basta. CR fica como “próximo nível” quando a WAN do CPE for endereçável.

Passo 1 — subir o ACS

Python 3.10+ e zero pip (só biblioteca padrão):

curl -fsSL https://www.mauroalexandre.com.br/wp-content/uploads/mauro-diretorio/acs-python-cwmp.py -o acs.py
python3 acs.py --port 7547 -v

Se o CPE tiver usuário/senha de Connection Request:

python3 acs.py --port 7547 -v --cr-user SEU_USER --cr-pass SUA_SENHA

No terminal aparece o bind e um prompt acs> (list, show, cr, gpv). Em outro shell, smoke sem roteador:

curl -fsSL https://www.mauroalexandre.com.br/wp-content/uploads/mauro-diretorio/acs-python-smoke_inform.py -o smoke_inform.py
python3 smoke_inform.py

Esperado: OK: InformResponse recebido. Se a 7547 já estiver com GenieACS no host, pare o serviço ou use outra porta nos dois lados.

Passo 2 — o que o código está fazendo (sem magia)

O arquivo é um único acs.py de propósito: dá para ler de cima a baixo na aula.

Memória: cada Inform vira um registro por serial (manufacturer, OUI, params, ConnectionRequestURL, fila de RPCs). Cai o processo, some o estado — de propósito.

SOAP: parse com xml.etree.ElementTree, namespaces urn:dslforum-org:cwmp-1-0/1-1/1-2 (CPE mistura). Resposta mínima de Inform:

<cwmp:InformResponse>
  <MaxEnvelopes>1</MaxEnvelopes>
</cwmp:InformResponse>

HTTP: ThreadingHTTPServer na 7547. POST com corpo → trata método; POST vazio → manda RPC da fila ou 204.

Connection Request: GET na URL do Inform; se vier 401 Digest, monta o challenge (comum em ZTE/Huawei).

Não tem TLS mTLS, nem multi-tenant, nem preset. É o miolo CWMP.

Passo 3 — configurar o CPE

No ZTE (menu varia: Network / Management / TR-069):

  1. ACS URL: http://ACS_IP:7547/
  2. Periodic Inform: Enable
  3. Intervalo: 60 no lab (em campo costuma ser bem maior)
  4. Connection Request user/pass: anote se for testar cr
  5. Salve e force Inform (reboot ou botão Inform, se existir)

Lembre da nota LAN/WAN acima. No meu teste, só pela WAN o H3601P falou com o ACS.

Passo 4 — o que você deve ver no log

Quando funciona, o ACS imprime algo nesta linha:

→ Inform serial=ZTEYH8TQ6U18507 events=['0 BOOTSTRAP'] manufacturer=ZTE
   param … SoftwareVersion = V9.0.0P10_MUL
   param … ConnectionRequestURL = http://100.x.x.x:7547/…
← InformResponse (MaxEnvelopes=1)
→ POST vazio …
← 204 No Content

Depois do periódico:

events=['2 PERIODIC']

No prompt:

acs> list
acs> show ZTEYH8TQ6U18507
acs> gpv ZTEYH8TQ6U18507 InternetGatewayDevice.DeviceInfo.SoftwareVersion

gpv enfileira o GetParameterValues e tenta o Connection Request. Se o CR URL estiver em CGNAT, o Inform periódico seguinte ainda roda; o “acordar agora” pode não.

O que levar para o GenieACS

Quando você voltar para o GenieACS, a UI deixa de ser mistério:

  • “device apareceu” = Inform + sessão HTTP que o ACS aceitou;
  • “Summon” = Connection Request no URL que o CPE anunciou;
  • fault / sessão estranha = SOAP/HTTP no meio do caminho, não “Mongo bravo” na primeira hipótese.

O ACS Python é o laboratório de protocolo. O GenieACS é a ferramenta de operação. Os dois se complementam — desde que a gente não confunda um com o outro.

Fechando

Se a meta é entender CWMP, um Inform + InformResponse bem lidos valem mais do que três slides de arquitetura. Sobe o script, aponta o CPE por um caminho que ele aceite (muitas vezes WAN), e olha o log.

Dúvida ou log de outro vendor: comenta no post. Gostou? Veja os cursos — TR-069 na prática, com lab.

Você pode gostar...

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

×