O SNMP não é o protocolo mais amigável: os arquivos MIB são muito longos e confusos e os OIDs são simplesmente impossíveis de serem lembrados. Mas e se você precisar trabalhar com o SNMP em Java? Por exemplo, escreva autotestes para verificar a API do servidor SNMP.
Por tentativa e erro, se houver uma quantidade escassa de informações sobre o assunto, ainda descobrimos como fazer amigos em Java e SNMP.
Nesta série de artigos, tentarei compartilhar a experiência adquirida com o protocolo. O primeiro artigo da série será dedicado à implementação do analisador de arquivos MIB em Java. Na segunda parte, falarei sobre como escrever um cliente SNMP. Na terceira parte, falaremos sobre um exemplo real do uso de uma biblioteca escrita: autotestes para verificar a interação com um dispositivo via SNMP.

Entrada
Tudo começou com a tarefa de escrever autotestes para verificar a operação do gravador de áudio e vídeo via SNMP. Para complicar a situação, não há muita informação sobre a interação com o SNMP em Java, especialmente quando se trata do segmento da Internet em idioma russo. Obviamente, pode-se olhar para C # ou Python. Mas em C #, a situação do protocolo é tão complicada quanto em Java. Existem algumas boas bibliotecas em python, mas já tínhamos uma infraestrutura pronta para os autotestes da API REST desse dispositivo em Java.
Como em qualquer outro protocolo de comunicação de rede, precisávamos de um cliente SNMP para trabalhar com vários tipos de solicitações. Os autotestes devem poder verificar o sucesso das solicitações GET e SET para parâmetros escalares e de tabela. Para tabelas, também era necessário poder verificar a adição e remoção de registros, se a própria tabela permitir essas operações.
Mas, além do cliente, a biblioteca precisava conter uma classe para trabalhar com arquivos MIB. Essa classe deve ter sido capaz de analisar o arquivo MIB para obter tipos de dados, limites de valores aceitáveis etc., para não codificar o que sempre pode ser alterado.
Ao procurar bibliotecas adequadas para Java, não encontramos uma única biblioteca que permitisse trabalhar com solicitações e arquivos MIB. Portanto, decidimos por duas bibliotecas diferentes. Para o cliente, a escolha da amplamente usada org.snmp4j.snmp4j (https://www.snmp4j.org) parecia bastante lógica e, para o analisador de arquivos MIB, a escolha foi feita para a não conhecida biblioteca net.percederberg.mibble (https: // www. mibble.org). Se com o snmp4j a escolha era óbvia, o mibble foi escolhido para a disponibilidade de documentação suficientemente detalhada (embora em inglês) com exemplos. Então, vamos começar.
Escrevendo um analisador de arquivos MIB
Todo mundo que já viu arquivos MIB sabe que isso é uma dor. Vamos tentar corrigir isso com a ajuda de um analisador simples, o que facilitará bastante a busca de informações de um arquivo, reduzindo-o a chamar um método específico.
Esse analisador pode ser usado como um utilitário separado para trabalhar com arquivos MIB ou incluído em qualquer outro projeto no SNMP, por exemplo, ao gravar um cliente SNMP ou ao testar a automação.
Preparação do projeto
Para facilitar a montagem, usamos o Maven. Dependendo da biblioteca add net.percederberg.mibble (https://www.mibble.org), o que facilitará o trabalho com arquivos MIB:
<dependency> <groupId>net.percederberg.mibble</groupId> <artifactId>mibble</artifactId> <version>2.9.3</version> </dependency>
Como ele não está no repositório central do Maven, adicionamos o seguinte código ao pom.xml:
<repositories> <repository> <id>opennms</id> <name>OpenNMS</name> <url>http://repo.opennms.org/maven2/</url> </repository> </repositories>
Se o projeto for montado usando o Maven sem erros, tudo estará pronto para o trabalho. Resta apenas criar uma classe de analisador (vamos chamá-lo de MIBParser) e importar tudo o que precisamos, a saber:
import net.percederberg.mibble.*;
Baixe e valide um arquivo MIB
Dentro da classe, haverá apenas um campo - um objeto do tipo net.percederberg.mibble.Mib para armazenar o arquivo MIB baixado:
private Mib mib;
Para carregar um arquivo, escrevemos este método:
private Mib loadMib(File file) throws MibLoaderException, IOException { MibLoader loader = new MibLoader(); Mib mib; file = file.getAbsoluteFile(); try { loader.addDir(file.getParentFile()); mib = loader.load(file); } catch (MibLoaderException e) { e.getLog().printTo(System.err); throw e; } catch (IOException e) { e.printStackTrace(); throw e; } return mib; }
A classe net.percederberg.mibble.MIBLoader valida o arquivo que estamos tentando carregar e lança net.percederberg.mibble.MibLoaderException se encontrar algum erro, incluindo erros de importação de outros arquivos MIB, se eles não esteja no mesmo diretório ou não contenha caracteres MIB importados.
No método loadMib, capturamos todas as exceções, escrevemos sobre elas no log e encaminhamos mais adiante, porque nesta fase, a continuação do trabalho é impossível - o arquivo não é válido.
Chamamos o método escrito no construtor do analisador:
public MIBParser(File file) throws MibLoaderException, IOException { if (!file.exists()) throw new FileNotFoundException("File not found in location: " + file.getAbsolutePath()); mib = loadMib(file.getAbsoluteFile()); if (!mib.isLoaded()) throw new MibLoaderException(file, "Not loaded."); }
Se o arquivo foi baixado e analisado com êxito, continue trabalhando.
Métodos para recuperar informações de um arquivo MIB
Usando os métodos da classe net.percederberg.mibble.Mib, é possível procurar caracteres individuais de um arquivo MIB por nome ou OID chamando os métodos getSymbol (String name) ou getSymbolByOid (String oid), respectivamente. Esses métodos retornam o objeto net.percederberg.mibble.MibSymbol, cujos métodos usaremos para obter as informações necessárias em um símbolo MIB específico.
Vamos começar com os métodos mais simples e de gravação para obter o nome de um símbolo por seu OID e, inversamente, por OID:
public String getName(String oid) { return mib.getSymbolByOid(oid).getName(); } public String getOid(String name) { String oid = null; MibSymbol s = mib.getSymbol(name); if (s instanceof MibValueSymbol) { oid = ((MibValueSymbol) s).getValue().toString(); if (((MibValueSymbol) s).isScalar()) oid = new OID(oid).append(0).toDottedString(); } return oid; }
Talvez esses sejam os recursos de um arquivo MIB específico, com o qual eu precisei trabalhar, mas, por algum motivo, os parâmetros escalares retornaram o OID sem zero no final; portanto, um código foi adicionado ao método para obter o OID, que, se o MIB o caractere é escalar, ele simplesmente adiciona ".0" ao OID recebido usando o método append (int index) da classe net.percederberg.mibble.OID. Se funcionar para você sem muleta, parabéns :)
Para obter o restante dos dados no símbolo, escrevemos um método auxiliar, onde obtemos o objeto net.percederberg.mibble.snmp.SnmpObjectType, que contém todas as informações necessárias sobre o símbolo MIB do qual ele foi obtido.
private SnmpObjectType getSnmpObjectType(MibSymbol symbol) { if (symbol instanceof MibValueSymbol) { MibType type = ((MibValueSymbol) symbol).getType(); if (type instanceof SnmpObjectType) { return (SnmpObjectType) type; } } return null; }
Por exemplo, podemos obter o tipo do símbolo MIB:
public String getType(String name) { MibSymbol s = mib.getSymbol(name); if (getSnmpObjectType(s).getSyntax().getReferenceSymbol() == null) return getSnmpObjectType(s).getSyntax().getName(); else return getSnmpObjectType(s).getSyntax().getReferenceSymbol().getName(); }
Existem 2 maneiras de obter o tipo, porque para tipos primitivos, a primeira opção funciona:
getSnmpObjectType(s).getSyntax().getName();
e para os importados, o segundo:
getSnmpObjectType(s).getSyntax().getReferenceSymbol().getName();
Você pode obter o nível de acesso de um personagem:
public String getAccess(String name) { MibSymbol s = mib.getSymbol(name); return getSnmpObjectType(s).getAccess().toString(); }
Valor mínimo válido para um parâmetro numérico:
public Integer getDigitMinValue(String name) { MibSymbol s = mib.getSymbol(name); String syntax = getSnmpObjectType(s).getSyntax().toString(); if (syntax.contains("STRING")) return null; Pattern p = Pattern.compile("(-?\\d+)..(-?\\d+)"); Matcher m = p.matcher(syntax); if (m.find()) { return Integer.parseInt(m.group(1)); } return null; }
Valor máximo permitido de um parâmetro numérico:
public Integer getDigitMaxValue(String name) { MibSymbol s = mib.getSymbol(name); String syntax = getSnmpObjectType(s).getSyntax().toString(); if (syntax.contains("STRING")) return null; Pattern p = Pattern.compile("(-?\\d+)..(-?\\d+)"); Matcher m = p.matcher(syntax); if (m.find()) { return Integer.parseInt(m.group(2)); } return null; }
Comprimento mínimo permitido da string (depende do tipo de string):
public Integer getStringMinLength(String name) { MibSymbol s = this.mib.getSymbol(name); String syntax = this.getSnmpObjectType(s).getSyntax().toString(); Pattern p = Pattern.compile("(-?\\d+)..(-?\\d+)"); Matcher m = p.matcher(syntax); return syntax.contains("STRING") && m.find()?Integer.valueOf(Integer.parseInt(m.group(1))):null; }
Comprimento máximo permitido da string (depende do tipo de string):
public Integer getStringMaxLength(String name) { MibSymbol s = mib.getSymbol(name); String syntax = getSnmpObjectType(s).getSyntax().toString(); Pattern p = Pattern.compile("(-?\\d+)..(-?\\d+)"); Matcher m = p.matcher(syntax); if (syntax.contains("STRING") && m.find()) { return Integer.parseInt(m.group(2)); } return null; }
Você também pode obter os nomes de todas as colunas da tabela por seu nome:
public ArrayList<String> getTableColumnNames(String tableName) { ArrayList<String> mibSymbolNamesList = new ArrayList<>(); MibValueSymbol table = (MibValueSymbol) mib.findSymbol(tableName, true); if (table.isTable() && table.getChild(0).isTableRow()) { MibValueSymbol[] symbols = table.getChild(0).getChildren(); for (MibValueSymbol mvs : symbols) { mibSymbolNamesList.add(mvs.getName()); } } return mibSymbolNamesList; }
Primeiro, de maneira padrão, obtemos um símbolo MIB pelo nome:
MibValueSymbol table = (MibValueSymbol) mib.findSymbol(tableName, true);
Em seguida, verificamos se é uma tabela e se o símbolo filho MIB é uma linha desta tabela e, se a condição retornar verdadeira, no loop, examinamos os filhos da linha da tabela e adicionamos o nome do elemento à matriz resultante:
if (table.isTable() && table.getChild(0).isTableRow()) { MibValueSymbol[] symbols = table.getChild(0).getChildren(); for (MibValueSymbol mvs : symbols) { mibSymbolNamesList.add(mvs.getName()); } }
Sumário
Esses métodos são suficientes para obter qualquer informação do arquivo MIB para cada caractere específico, sabendo apenas seu nome. Por exemplo, ao escrever um cliente SNMP, você pode incluir esse analisador para que os métodos do cliente não aceitem OIDs, mas nomes de símbolos MIB. Isso aumentará a confiabilidade do código, conforme um erro de digitação no OID pode não levar ao caractere ao qual queremos nos referir. E sem OIDs - sem problemas.
A vantagem é a legibilidade do código, o que significa sua capacidade de manutenção. É mais fácil entender a essência do projeto se o código funcionar com nomes humanos.
Outra aplicação é a automação de teste. Nos dados de teste, é possível obter valores de limite de parâmetros numéricos dinamicamente a partir de um arquivo MIB. Portanto, se os valores de limite de alguns símbolos MIB na nova versão do componente testado forem alterados, você não precisará alterar o código de autoteste.
Em geral, usando um analisador, trabalhar com arquivos MIB se torna muito mais agradável e deixa de ser uma dor.