SNMP并不是最用户友好的协议:MIB文件太长且令人困惑,并且OID简直无法记住。 但是,如果您需要在Java中使用SNMP,该怎么办? 例如,编写自动测试以检查SNMP服务器API。
通过反复试验,如果关于该主题的信息很少,我们仍然想出了如何使Java和SNMP成为朋友。
在本系列文章中,我将尝试分享该协议获得的经验。 该系列的第一篇文章将专门介绍Java中MIB文件解析器的实现。 在第二部分中,我将讨论编写SNMP客户端。 在第三部分中,我们将讨论一个使用书面库的真实示例:自动测试以通过SNMP验证与设备的交互。

参赛作品
这一切都始于编写自我测试以通过SNMP验证音频视频记录器的操作的任务。 使情况复杂化的是,在Java中与SNMP交互的信息并不多,尤其是在Internet的俄语部分。 当然,可以考虑使用C#或Python。 但是在C#中,协议情况与Java中一样复杂。 python中有几个不错的库,但是我们已经有了Java对该设备的REST API自动测试的现成基础结构。
与任何其他网络通信协议一样,我们需要SNMP客户端来处理各种类型的请求。 自动测试应该能够验证对标量和表参数的GET和SET请求是否成功。 对于表,如果表本身允许进行这些操作,则还需要能够验证记录的添加和删除。
但是除了客户端之外,该库还必须包含一个用于处理MIB文件的类。 此类应能够解析MIB文件以获得数据类型,可接受值的边界等,以免硬编码总是可以更改的内容。
在寻找适用于Java的库时,我们没有找到可以同时处理请求和MIB文件的库。 因此,我们选择了两个不同的库。 对于客户端来说,选择广泛使用的org.snmp4j.snmp4j(https://www.snmp4j.org)似乎是合乎逻辑的,对于MIB文件的解析器,选择是对不太知名的net.percederberg.mibble库(https:// www。 mibble.org)。 如果使用snmp4j的选择是显而易见的,那么选择mibble是为了获得足够详细(带有英语)文档和示例的可用性。 因此,让我们开始吧。
编写MIB文件解析器
曾经看过MIB文件的每个人都知道这很痛苦。 让我们尝试在一个简单的解析器的帮助下解决此问题,该解析器将极大地方便从文件中搜索信息,并将其减少为调用特定方法。
这样的解析器可以用作处理MIB文件的单独实用程序,也可以包含在SNMP下的任何其他项目中,例如,在编写SNMP客户端或测试自动化时。
项目准备
为了便于组装,我们使用Maven。 取决于添加的net.percederberg.mibble库(https://www.mibble.org),这将使我们更轻松地使用MIB文件:
<dependency> <groupId>net.percederberg.mibble</groupId> <artifactId>mibble</artifactId> <version>2.9.3</version> </dependency>
由于它不在中央Maven存储库中,因此我们将以下代码添加到pom.xml中:
<repositories> <repository> <id>opennms</id> <name>OpenNMS</name> <url>http://repo.opennms.org/maven2/</url> </repository> </repositories>
如果使用Maven正确组装了项目,则一切准备就绪。 剩下的只是创建一个解析器类(我们称之为MIBParser)并导入我们需要的所有内容,即:
import net.percederberg.mibble.*;
下载并验证MIB文件
在该类内,将只有一个字段-一个net.percederberg.mibble.Mib类型的对象,用于存储下载的MIB文件:
private Mib mib;
要上传文件,我们编写此方法:
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; }
net.percederberg.mibble.MIBLoader类会验证我们尝试加载的文件,并在发现任何错误(包括从其他MIB文件导入的错误)时抛出net.percederberg.mibble.MibLoaderException异常。不要位于同一目录中或不包含导入的MIB字符。
在loadMib方法中,我们捕获了所有异常,将其写入日志并进一步转发,因为 在此阶段,无法继续工作-该文件无效。
我们在解析器构造函数中调用书面方法:
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."); }
如果文件已成功下载并解析,请继续工作。
从MIB文件检索信息的方法
使用net.percederberg.mibble.Mib类的方法,可以分别通过调用getSymbol(字符串名称)或getSymbolByOid(字符串oid)方法,通过名称或OID搜索MIB文件的各个字符。 这些方法返回net.percederberg.mibble.MibSymbol对象,我们将使用该方法来获取有关特定MIB符号的必要信息。
让我们从最简单的write方法开始,该方法用于通过符号的OID获取符号的名称,反之,通过名称获取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; }
也许这些是我需要使用的特定MIB文件的功能,但是由于某些原因,标量参数返回的OID最终不带零,因此将代码添加到获取OID的方法中,如果MIB该字符是标量,它只是使用net.percederberg.mibble.OID类中的append(int index)方法将“ .0”添加到接收的OID中。 如果它对您没有帮助,那么恭喜您:)
为了获得符号上的其余数据,我们编写了一个辅助方法,即获得对象net.percederberg.mibble.snmp.SnmpObjectType,该对象包含有关从中获取符号的MIB符号的所有必要信息。
private SnmpObjectType getSnmpObjectType(MibSymbol symbol) { if (symbol instanceof MibValueSymbol) { MibType type = ((MibValueSymbol) symbol).getType(); if (type instanceof SnmpObjectType) { return (SnmpObjectType) type; } } return null; }
例如,我们可以获取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(); }
有两种获取类型的方法,因为 对于基本类型,第一个选项有效:
getSnmpObjectType(s).getSyntax().getName();
对于进口的,第二个:
getSnmpObjectType(s).getSyntax().getReferenceSymbol().getName();
您可以获取角色的访问级别:
public String getAccess(String name) { MibSymbol s = mib.getSymbol(name); return getSnmpObjectType(s).getAccess().toString(); }
数字参数的最小有效值:
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; }
数字参数的最大允许值:
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; }
允许的最小字符串长度(取决于字符串的类型):
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; }
允许的最大字符串长度(取决于字符串的类型):
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; }
您还可以通过其名称获取表中所有列的名称:
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; }
首先,以一种标准方式,通过名称获得一个MIB符号:
MibValueSymbol table = (MibValueSymbol) mib.findSymbol(tableName, true);
然后我们检查它是否是一个表,并且子MIB符号是否是该表的一行,如果条件返回true,则在循环中,我们遍历表行的子项,并将元素名称添加到结果数组中:
if (table.isTable() && table.getChild(0).isTableRow()) { MibValueSymbol[] symbols = table.getChild(0).getChildren(); for (MibValueSymbol mvs : symbols) { mibSymbolNamesList.add(mvs.getName()); } }
总结
这些方法足以从MIB文件中获取每个特定字符的任何信息,而仅知道其名称。 例如,编写SNMP客户端时,可以在其中包含这样的解析器,以便客户端方法不接受OID,但接受MIB符号名。 这将提高代码的可靠性,因为 OID中的错字可能不会导致我们要引用的字符。 而且没有OID-没有问题。
加上是代码的可读性,这意味着代码的可维护性。 如果代码以人名操作,则更容易理解项目的本质。
另一个应用是测试自动化。 在测试数据中,可以从MIB文件中动态获取数值参数的边界值。 因此,如果新版本的已测试组件中某些MIB符号的边界值发生了变化,则无需更改自动测试代码。
通常,使用解析器来处理MIB文件变得更加愉快,并且它们不再是一件痛苦的事情。