SNMP + Java - persönliche Erfahrung. Schreiben eines Parsers für MIB-Dateien

SNMP ist nicht das benutzerfreundlichste Protokoll: MIB-Dateien sind zu lang und verwirrend, und OIDs sind einfach nicht zu merken. Aber was ist, wenn Sie mit SNMP in Java arbeiten müssen? Schreiben Sie beispielsweise Autotests, um die SNMP-Server-API zu überprüfen.

Durch Versuch und Irrtum, wenn es eine ziemlich geringe Menge an Informationen zu diesem Thema gibt, haben wir immer noch herausgefunden, wie man Freunde Java und SNMP macht.

In dieser Artikelserie werde ich versuchen, die mit dem Protokoll gewonnenen Erfahrungen zu teilen. Der erste Artikel der Reihe befasst sich mit der Implementierung des Parsers von MIB-Dateien in Java. Im zweiten Teil werde ich über das Schreiben eines SNMP-Clients sprechen. Im dritten Teil werden wir über ein reales Beispiel für die Verwendung einer schriftlichen Bibliothek sprechen: Autotests zur Überprüfung der Interaktion mit einem Gerät über SNMP.



Eintrag


Alles begann mit der Aufgabe, Selbsttests zu schreiben, um den Betrieb des Audio-Video-Rekorders über SNMP zu überprüfen. Erschwerend kommt hinzu, dass es nicht viele Informationen zur Interaktion mit SNMP in Java gibt, insbesondere wenn es um das russischsprachige Segment des Internets geht. Natürlich könnte man in Richtung C # oder Python schauen. In C # ist die Protokollsituation jedoch ungefähr so ​​kompliziert wie in Java. Es gibt einige gute Bibliotheken in Python, aber wir hatten bereits eine vorgefertigte Infrastruktur für die REST-API-Autotests dieses Geräts in Java.

Wie bei jedem anderen Netzwerkkommunikationsprotokoll benötigten wir einen SNMP-Client, um mit verschiedenen Arten von Anforderungen arbeiten zu können. Autotests sollten in der Lage sein, den Erfolg von GET- und SET-Anforderungen für Skalar- und Tabellenparameter zu überprüfen. Für Tabellen musste es auch möglich sein, das Hinzufügen und Löschen von Datensätzen zu überprüfen, wenn die Tabelle selbst diese Vorgänge zulässt.

Zusätzlich zum Client musste die Bibliothek eine Klasse für die Arbeit mit MIB-Dateien enthalten. Diese Klasse sollte in der Lage sein, die MIB-Datei zu analysieren, um Datentypen, Grenzen akzeptabler Werte usw. abzurufen, um nicht fest zu codieren, was sich immer ändern kann.

Bei der Suche nach geeigneten Bibliotheken für Java haben wir keine einzige Bibliothek gefunden, die die Arbeit mit Anforderungen und MIB-Dateien ermöglicht. Deshalb haben wir uns für zwei verschiedene Bibliotheken entschieden. Für den Client schien die Auswahl der weit verbreiteten org.snmp4j.snmp4j (https://www.snmp4j.org) ziemlich logisch, und für den Parser von MIB-Dateien wurde die Auswahl für die nicht sehr bekannte net.percederberg.mibble-Bibliothek (https: // www. mibble.org). Wenn bei snmp4j die Wahl offensichtlich war, wurde mibble ausgewählt, um eine ausreichend detaillierte (wenn auch englischsprachige) Dokumentation mit Beispielen zu erhalten. Also fangen wir an.

Schreiben eines Parsers für MIB-Dateien


Jeder, der jemals MIB-Dateien gesehen hat, weiß, dass dies ein Schmerz ist. Versuchen wir, dies mithilfe eines einfachen Parsers zu beheben, der die Suche nach Informationen aus einer Datei erheblich erleichtert und sie auf den Aufruf einer bestimmten Methode reduziert.

Ein solcher Parser kann als separates Dienstprogramm für die Arbeit mit MIB-Dateien verwendet oder in einem anderen Projekt unter SNMP enthalten sein, z. B. beim Schreiben eines SNMP-Clients oder beim Testen der Automatisierung.

Projektvorbereitung


Zur Erleichterung der Montage verwenden wir Maven. Abhängig von der Bibliothek net.percederberg.mibble hinzufügen (https://www.mibble.org), die uns die Arbeit mit MIB-Dateien erleichtert:

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

Da es nicht im zentralen Maven-Repository liegt, fügen wir pom.xml den folgenden Code hinzu:

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

Wenn das Projekt mit dem Maven fehlerfrei zusammengestellt wird, ist alles betriebsbereit. Es bleibt nur noch eine Parser-Klasse zu erstellen (nennen wir sie MIBParser) und alles zu importieren, was wir brauchen, nämlich:

 import net.percederberg.mibble.*; 

Laden Sie eine MIB-Datei herunter und validieren Sie sie


Innerhalb der Klasse gibt es nur ein Feld - ein Objekt vom Typ net.percederberg.mibble.Mib zum Speichern der heruntergeladenen MIB-Datei:

 private Mib mib; 

Um eine Datei hochzuladen, schreiben wir diese Methode:

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

Die Klasse net.percederberg.mibble.MIBLoader überprüft die Datei, die wir laden möchten, und löst die Ausnahme net.percederberg.mibble.MibLoaderException aus, wenn darin Fehler gefunden werden, einschließlich Importfehlern aus anderen MIB-Dateien liegen nicht im selben Verzeichnis oder enthalten keine importierten MIB-Zeichen.

In der loadMib-Methode fangen wir alle Ausnahmen ab, schreiben darüber in das Protokoll und leiten sie weiter, weil Zu diesem Zeitpunkt ist die Fortsetzung der Arbeit nicht möglich - die Datei ist ungültig.

Wir rufen die geschriebene Methode im Parser-Konstruktor auf:

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

Wenn die Datei erfolgreich heruntergeladen und analysiert wurde, arbeiten Sie weiter.

Methoden zum Abrufen von Informationen aus einer MIB-Datei


Mit den Methoden der Klasse net.percederberg.mibble.Mib können Sie nach einzelnen Zeichen einer MIB-Datei nach Namen oder OID suchen, indem Sie die Methoden getSymbol (String name) bzw. getSymbolByOid (String oid) aufrufen. Diese Methoden geben das Objekt net.percederberg.mibble.MibSymbol zurück, mit dessen Methoden wir die erforderlichen Informationen zu einem bestimmten MIB-Symbol abrufen.

Beginnen wir mit den einfachsten und schreiben Sie Methoden, um den Namen eines Symbols anhand seiner OID und umgekehrt anhand seines Namens zu ermitteln:

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

Vielleicht sind dies die Merkmale einer bestimmten MIB-Datei, mit der ich arbeiten musste, aber aus irgendeinem Grund gaben die skalaren Parameter am Ende OID ohne Null zurück, sodass der Methode zum Abrufen der OID ein Code hinzugefügt wurde, der, wenn die MIB Das Zeichen ist skalar und fügt der empfangenen OID einfach „.0“ hinzu, indem die Methode append (int index) aus der Klasse net.percederberg.mibble.OID verwendet wird. Wenn es für Sie ohne Krücke funktioniert, herzlichen Glückwunsch :)

Um den Rest der Daten auf dem Symbol zu erhalten, schreiben wir eine Hilfsmethode, bei der wir das Objekt net.percederberg.mibble.snmp.SnmpObjectType erhalten, das alle erforderlichen Informationen über das MIB-Symbol enthält, von dem es erhalten wurde.

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

Zum Beispiel können wir den Typ des MIB-Symbols erhalten:

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

Es gibt zwei Möglichkeiten, um Typ zu bekommen, weil Für primitive Typen funktioniert die erste Option:

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

und für importierte die zweite:

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

Sie können die Zugriffsebene eines Charakters erhalten:

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

Minimaler gültiger Wert für einen numerischen Parameter:

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

Maximal zulässiger Wert eines numerischen Parameters:

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

Minimal zulässige Zeichenfolgenlänge (abhängig vom Zeichenfolgentyp):

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

Maximal zulässige Zeichenfolgenlänge (abhängig vom Zeichenfolgentyp):

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

Sie können die Namen aller Spalten in der Tabelle auch anhand ihres Namens abrufen:

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

Zunächst erhalten wir standardmäßig ein MIB-Symbol mit Namen:

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

Dann prüfen wir, ob es sich um eine Tabelle handelt und ob das untergeordnete MIB-Symbol eine Zeile dieser Tabelle ist. Wenn die Bedingung true zurückgibt, gehen wir in der Schleife die untergeordneten Elemente der Tabellenzeile durch und fügen dem resultierenden Array den Elementnamen hinzu:

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

Zusammenfassung


Diese Methoden reichen aus, um Informationen aus der MIB-Datei für jedes bestimmte Zeichen abzurufen, wobei nur dessen Name bekannt ist. Wenn Sie beispielsweise einen SNMP-Client schreiben, können Sie einen solchen Parser darin einfügen, sodass Client-Methoden keine OIDs, sondern MIB-Symbolnamen akzeptieren. Dies erhöht die Zuverlässigkeit des Codes Ein Tippfehler in OID führt möglicherweise nicht zu dem Zeichen, auf das wir verweisen möchten. Und keine OIDs - keine Probleme.

Das Plus ist die Lesbarkeit des Codes, dh seine Wartbarkeit. Es ist einfacher, die Essenz des Projekts zu verstehen, wenn der Code mit menschlichen Namen arbeitet.

Eine weitere Anwendung ist die Testautomatisierung. In den Testdaten kann man aus einer MIB-Datei dynamisch Grenzwerte numerischer Parameter erhalten. Wenn sich also die Grenzwerte einiger MIB-Symbole in der neuen Version der getesteten Komponente ändern, müssen Sie den Autotest-Code nicht ändern.

Im Allgemeinen wird die Arbeit mit MIB-Dateien mit einem Parser viel angenehmer, und sie sind nicht mehr so ​​schmerzhaft.

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


All Articles