SNMP + Java: experiencia personal. Escribir un analizador de archivos MIB

SNMP no es el protocolo más fácil de usar: los archivos MIB son demasiado largos y confusos, y los OID son simplemente imposibles de recordar. Pero, ¿qué pasa si necesita trabajar con SNMP en Java? Por ejemplo, escriba autotests para verificar la API del servidor SNMP.

Por prueba y error, si hay una cantidad bastante escasa de información sobre el tema, todavía descubrimos cómo hacer amigos Java y SNMP.

En esta serie de artículos intentaré compartir la experiencia adquirida con el protocolo. El primer artículo de la serie estará dedicado a la implementación del analizador de archivos MIB en Java. En la segunda parte, hablaré sobre cómo escribir un cliente SNMP. En la tercera parte, hablaremos de un ejemplo real de uso de una biblioteca escrita: pruebas automáticas para verificar la interacción con un dispositivo a través de SNMP.



Entrada


Todo comenzó con la tarea de escribir autocomprobaciones para verificar el funcionamiento de la grabadora de audio y video a través de SNMP. Para complicar la situación, no hay mucha información sobre la interacción con SNMP en Java, especialmente cuando se trata del segmento de Internet en ruso. Por supuesto, uno podría mirar hacia C # o Python. Pero en C #, la situación del protocolo es tan complicada como en Java. Hay un par de buenas bibliotecas en python, pero ya teníamos una infraestructura preparada para las pruebas automáticas API REST de este dispositivo en Java.

Al igual que con cualquier otro protocolo de comunicación de red, necesitábamos un cliente SNMP para trabajar con varios tipos de solicitudes. Las pruebas automáticas deben poder verificar el éxito de las solicitudes GET y SET para los parámetros escalares y de tabla. Para las tablas, también se requería poder verificar la adición y eliminación de registros, si la tabla misma permite estas operaciones.

Pero además del cliente, la biblioteca tenía que contener una clase para trabajar con archivos MIB. Esta clase debería haber podido analizar el archivo MIB para obtener tipos de datos, límites de valores aceptables, etc., para no codificar lo que siempre puede cambiar.

Al buscar bibliotecas adecuadas para Java, no encontramos una sola biblioteca que permitiera trabajar tanto con solicitudes como con archivos MIB. Por lo tanto, nos decidimos por dos bibliotecas diferentes. Para el cliente, la elección de la org.snmp4j.snmp4j (https://www.snmp4j.org) ampliamente utilizada parecía bastante lógica, y para el analizador de archivos MIB, la elección se hizo a la biblioteca net.percederberg.mibble no muy conocida (https: // www. mibble.org). Si con snmp4j la elección era obvia, entonces se eligió mibble por la disponibilidad de documentación suficientemente detallada (aunque en inglés) con ejemplos. Entonces comencemos.

Escribir un analizador de archivos MIB


Todos los que han visto archivos MIB saben que esto es un dolor. Intentemos solucionar esto con la ayuda de un analizador simple, que facilitará en gran medida la búsqueda de información de un archivo, reduciéndolo a llamar a un método en particular.

Dicho analizador puede usarse como una utilidad separada para trabajar con archivos MIB o incluirse en cualquier otro proyecto bajo SNMP, por ejemplo, cuando se escribe un cliente SNMP o se prueba la automatización.

Preparación del proyecto


Para facilitar el montaje, utilizamos Maven. Dependiendo de la biblioteca add net.percederberg.mibble (https://www.mibble.org), lo que nos facilitará trabajar con archivos MIB:

<dependency> <groupId>net.percederberg.mibble</groupId> <artifactId>mibble</artifactId> <version>2.9.3</version> </dependency> 

Como no se encuentra en el repositorio central de Maven, agregamos el siguiente código a pom.xml:

 <repositories> <repository> <id>opennms</id> <name>OpenNMS</name> <url>http://repo.opennms.org/maven2/</url> </repository> </repositories> 

Si el proyecto se ensambla utilizando Maven sin errores, todo está listo para el trabajo. Solo queda crear una clase de analizador (llamémosla MIBParser) e importar todo lo que necesitamos, a saber:

 import net.percederberg.mibble.*; 

Descargue y valide un archivo MIB


Dentro de la clase solo habrá un campo: un objeto de tipo net.percederberg.mibble.Mib para almacenar el archivo MIB descargado:

 private Mib mib; 

Para cargar un archivo, escribimos 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; } 

La clase net.percederberg.mibble.MIBLoader valida el archivo que estamos intentando cargar y lanza net.percederberg.mibble.MibLoaderException si encuentra algún error en él, incluidos los errores de importación de otros archivos MIB, si no se encuentran en el mismo directorio o no contienen caracteres MIB importados.

En el método loadMib, capturamos todas las excepciones, escribimos sobre ellas en el registro y reenviamos más, porque En esta etapa, la continuación del trabajo es imposible: el archivo no es válido.

Llamamos al método escrito en el constructor del analizador sintáctico:

 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."); } 

Si el archivo se descargó y analizó correctamente, continúe trabajando.

Métodos para recuperar información de un archivo MIB


Usando los métodos de la clase net.percederberg.mibble.Mib, puede buscar caracteres individuales de un archivo MIB por nombre u OID llamando a los métodos getSymbol (String name) o getSymbolByOid (String oid), respectivamente. Estos métodos devuelven el objeto net.percederberg.mibble.MibSymbol, cuyos métodos usaremos para obtener la información necesaria sobre un símbolo MIB específico.

Comencemos con los métodos más simples y de escritura para obtener el nombre de un símbolo por su OID y, por el contrario, OID por nombre:

 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; } 

Quizás estas son las características de un archivo MIB específico, con el que necesitaba trabajar, pero por alguna razón, los parámetros escalares devolvieron OID sin cero al final, por lo que se agregó un código al método para obtener OID, que, si el MIB el carácter es escalar, simplemente agrega ".0" al OID recibido utilizando el método append (int index) de la clase net.percederberg.mibble.OID. Si te funciona sin una muleta, felicidades :)

Para obtener el resto de los datos sobre el símbolo, escribimos un método auxiliar, donde obtenemos el objeto net.percederberg.mibble.snmp.SnmpObjectType, que contiene toda la información necesaria sobre el símbolo MIB del que se obtuvo.

 private SnmpObjectType getSnmpObjectType(MibSymbol symbol) { if (symbol instanceof MibValueSymbol) { MibType type = ((MibValueSymbol) symbol).getType(); if (type instanceof SnmpObjectType) { return (SnmpObjectType) type; } } return null; } 

Por ejemplo, podemos obtener el tipo del 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(); } 

Hay 2 formas de escribir, porque Para los tipos primitivos, la primera opción funciona:

 getSnmpObjectType(s).getSyntax().getName(); 

y para los importados, el segundo:

 getSnmpObjectType(s).getSyntax().getReferenceSymbol().getName(); 

Puedes obtener el nivel de acceso de un personaje:

 public String getAccess(String name) { MibSymbol s = mib.getSymbol(name); return getSnmpObjectType(s).getAccess().toString(); } 

Valor mínimo válido para un 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 un 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; } 

Longitud mínima permitida de la cadena (depende del tipo de cadena):

 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; } 

Longitud máxima permitida de la cadena (depende del tipo de cadena):

 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; } 

También puede obtener los nombres de todas las columnas de la tabla por su nombre:

 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; } 

Primero, de manera estándar, obtenemos un símbolo MIB por nombre:

 MibValueSymbol table = (MibValueSymbol) mib.findSymbol(tableName, true); 

Luego verificamos si es una tabla y que el símbolo MIB secundario es una fila de esta tabla y, si la condición devuelve verdadero, en el bucle pasamos por los elementos secundarios de la fila de la tabla y agregamos el nombre del elemento a la matriz resultante:

 if (table.isTable() && table.getChild(0).isTableRow()) { MibValueSymbol[] symbols = table.getChild(0).getChildren(); for (MibValueSymbol mvs : symbols) { mibSymbolNamesList.add(mvs.getName()); } } 

Resumen


Estos métodos son suficientes para obtener cualquier información del archivo MIB para cada carácter específico, conociendo solo su nombre. Por ejemplo, al escribir un cliente SNMP, puede incluir un analizador de este tipo para que los métodos del cliente no acepten OID, sino nombres de símbolos MIB. Esto aumentará la confiabilidad del código, ya que un error tipográfico en OID puede no conducir al carácter al que queremos referirnos. Y sin OID, sin problemas.

La ventaja es la legibilidad del código, lo que significa su facilidad de mantenimiento. Es más fácil entender la esencia del proyecto si el código funciona con nombres humanos.

Otra aplicación es la automatización de pruebas. En los datos de prueba, uno puede obtener valores límite de parámetros numéricos dinámicamente de un archivo MIB. Por lo tanto, si los valores límite de algunos símbolos MIB en la nueva versión del componente probado cambian, no tendrá que cambiar el código de prueba automática.

En general, usar un analizador sintáctico, trabajar con archivos MIB se vuelve mucho más placentero y dejan de ser tan molestos.

Source: https://habr.com/ru/post/es427267/


All Articles