Pitch: Inkcheck: Como implementei um CLI em Rust para monitorar o SNMP proprietário da Brother
Fala, pessoal do TabNews! 👋
Neste meu primeiro post por aqui, queria compartilhar com vocês a história do Inkcheck, um CLI escrito em Rust que nasceu da simples necessidade de "saber se tem toner preto na impressora" e que, por causa da forma que a Brother implementou sua comunicação pelo protocolo SNMP, acabou se tornando um baita estudo sobre Rust, protocolos de rede, engenharia reversa etc. Além de trazer para o mundo real uma ferramenta opensource que pode ajudar outras pessoas a não passarem pelos mesmos problemas.
O Vilão: brInfoMaintenance 🖨️💀
Trabalho em uma empresa dominada por impressoras Brother. O hardware até aguenta o tranco, mas a interface web de gerenciamento parece ter sido feita em 1998 por alguém que nutria um ódio profundo por CSS (não julgo).
Todo mundo sabe que impressoras foram enviadas à Terra para testar a sanidade dos profissionais de TI. Mas a Brother elevou isso a outro nível.
O problema é que, enquanto marcas civilizadas entregam o nível de suprimentos via SNMP usando MIBs padronizadas, a Brother optou pelo caos. Ela esconde nível de toner, cilindro, fusor e erros dentro de uma única OID proprietária: a brInfoMaintenance (.1.3.6.1.4.1.2435.2.3.9.4.2.1.5.5.8.0).
Ao dar um GET nessa OID, você não recebe um inteiro amigável de 0 a 100. Você recebe um blob hexadecimal gigante e não documentado. ADORÁVEL!
A Anatomia do Caos
Para vocês terem ideia, esse é um exemplo da resposta da impressora:
04 00 00 00 01 7F 01 04 00 00 00 01 68 01 04 00
00 00 01 55 01 04 00 00 00 01 31 01 04 00 00 00
01 32 01 04 00 00 00 02 33 01 04 00 00 00 01 34
01 04 00 00 00 01 70 01 04 00 00 01 F4 82 01 04
00 00 00 0A 71 01 04 00 00 22 60 83 01 04 00 00
00 5A 72 01 04 00 00 19 64 84 01 04 00 00 00 46
6F 01 04 00 00 25 1C 81 01 04 00 00 00 64 79 01
04 00 00 0C 1C 7A 01 04 00 00 0C 1C 7B 01 04 00
00 0C 1C 80 01 04 00 00 0C 1C 69 01 04 00 00 25
E4 73 01 04 00 00 28 FA 74 01 04 00 00 28 FA 75
01 04 00 00 28 FA 7E 01 04 00 00 28 FA 54 01 04
00 00 00 01 35 01 04 00 00 00 01 6A 01 04 00 00
21 34 6D 01 04 00 00 26 AC FF
Na prática, se abrissem um chamado de uma impressora que parou de funcionar em um setor da empresa, eu tinha que abrir o navegador, enfrentar o login lento e navegar por menus infinitos para descobrir que... o papel acabou.
Eu me recusei a aceitar isso. Numa bela noite, escavando as profundezas do Google (página 2 pra frente, onde habitam os monstros), achei uma luz: um blog em espanhol chamado 2 Tazas de Linux.
O Mapa do Tesouro (e o MVP em Python) 🐍
O autor desse blog fez o trabalho hercúleo e altamente insalubre de mapear quais bytes daquela string hexadecimal correspondiam a quais suprimentos.
🔗 Fonte do milagre: https://2tazasdelinux.blogspot.com/2021/05/
Com isso em mãos, resolvi fazer um script em Python que fazia a request SNMP, fazia a leitura daqueles bytes hexadecimais e procurava o código correspondente ao toner preto, aplicava o procedimento para trazer o valor do toner e exibir na tela. Deixei isso documentado nesse repositório.
Ele funcionava e resolvia meu problema imediato. Mas eu queria algo mais robusto, mas não tão robusto quanto um script para colocar no Zabbix.
Queria algo para consultas rápidas, um binário estático e leve que eu pudesse jogar em qualquer lugar sem instalar dependências e que funcionasse para qualquer impressora, não só com impressoras Brother.
A Evolução: Nasce o Inkcheck 🚀
Como eu já estava a um tempo estudando Rust, já havia feito alguma APIs e CRUDs para estudo e era uma linguagem que eu estava gostando de conhecer, resolvi escrever em Rust mesmo. Escolhi a linguagem porque achei que seria um desafio legal, pois nunca havia feito um CLI antes, não fazia ideia como funcionava, mas tinha a curiosidade.
A ideia era criar uma engine genérica onde eu pudesse plugar "drivers" (arquivos JSON com as OIDS) que mostram onde buscar a informação. Assim, consegui manter a lógica complexa de decodificação da Brother e ainda assim abrir as portas para Xerox, HP, Epson etc.
O Boss Final: O Mistério do SNMPv3 🕵️
Como todo bom projeto de estudo, acabei esbarrando em limitações do mundo real. O Inkcheck funcionava bem, mas ao tentar implementar suporte ao SNMPv3 (para ter autenticação e criptografia), as coisas complicaram.
Funcionava na Xerox. Funcionava na Epson. Na Brother? Timeout.
Testei em várias impressoras Brother, vários modelos, nenhuma funcionou.
Imediatamente pensei na solução: Álcool e fósforo Ler a RFC! É claro!!
Lendo a RFC do SNMP eu descobri que o protocolo tem um campo chamado “Context Name”. A maioria dos dispositivos ignora isso ou aceita vazio. A Brother não, ela é diferente, e eu não esperava menos dela. Ela requer que esse campo venha preenchido.
E para piorar, outro modelo de impressora que temos aqui na empresa como as OKI B431 são ainda mais teimosas: elas sequer permitem alterar esse contexto na interface, é o que veio de fábrica e boa sorte.
O Patch na Unha
Pois bem, vasculhando o código da crate que eu estava usando para as requests SNMP (snmp2) no Github, vi que tinha esse campo hardcoded como vazio (&[]).
Trocar de crate não era uma opção viável pra mim, pois o ecossistema SNMP em Rust ainda é pequeno e não tem muitas crates maduras, a snmp2 realmente me parece a mais completa por enquanto.
Então, fiz o que qualquer dev curioso faria, resolvi encarar o código da biblioteca.
Mesmo sem muita experiência, fiz um fork da snmp2, estudei como os outros parâmetros eram passados e implementei um método with_context_name(context_name: &str), tentando seguir o padrão de design da crate.
Abri um PR no repositório oficial (se vai ser aceito, só Deus sabe), mas configurei meu Cargo.toml para usar minha versão "patcheada".
Ver o terminal retornar os dados daquela impressora teimosa foi uma vitória extremamente gratificante, principalmente por ter sentido na pele erro atrás de erro até ali.
O Inkcheck não é perfeito. Longe disso. Sou um dev iniciante e tenho certeza que meu código faria um Rustacean purista ter calafrios.
Mas ele resolve meu problema, uso no meu dia a dia, é rápido e me tirou da zona de conforto, além de ter sido um ótimo projeto para entender como funciona um CLI, SemVer, CI/CD (tem build automático para Linux, Mac e até instalador MSI para Windows) e me fez criar familiaridade com a linguagem que eu queria aprender.
Ainda vou continuar implementando features nele quando sobrar tempo.
Obrigado por ter ficado até aqui e se você também sofre com impressoras ou só quer dar uma olhada no projeto, dá uma passada lá:
🔗 Repositório: https://github.com/allansomensi/inkcheck
P.S.: Se você usa impressora Brother e testar, me avisa se funcionou ou se a impressora explodiu! 💣