当前位置: 首页 > 工具软件 > SNMP4J > 使用案例 >

snmp4j详解

夏侯昆琦
2023-12-01

    关于Snmp4j包,最重要三个概念,也是三个类:Snmp、Target、PDU
   
    Target,表示被管理的设备。对于一个被管理的设备,我们关注它的Address,它使用的协议版本version,访问它的用户名和密码、以及对这个设备我们设置跟它打交道的一些策略,比如超时时间、重传次数等。所以有如下方法:
    1)获取或者设置ip地址
    void setAddress(Address address)  
    Address getAddress()
    2)设置或者获取版本号(支持V1,V2c,V3)
    void setVersion(int ver)
    int   getVersion()
    3)超时时间 ( 单位:milliseconds )
    void   setTimeout(long out)
    long getTimeout()
    4)重传次数
    void   setRetries(int retries)
    int   getRetries()
    5)最大PDU大小值
    void setMaxSizeRequestPDU(int max)
    int   getMaxSizeRequestPDU()

    上面这些对于Snmp三个版本都是相同的,不同的只是在于安全方面,具体来说,Snmpv1,v2c采用共同体(community)方式,安全性很差;SnmpV3采用基于用户的安全(USM)方式,安全性能得到很大提高。
   (1) 对于Snmpv1,v2c,使用Target派生出一个子类CommunityTarget,增加了对Community的方法:
        void setCommunity(OctetString community)
        OctetString getCommunity()

    (2)对于SnmpV3,使用其子类UserTarget,增加了一些有关USM安全方面的设置,比如采用的安全模型、安全级别、访问该设备的用户名以及权威引擎ID(authoritative engine ID):
       i)安全级别(支持三种安全级别)
       void setSecurityLevel(int level)
       int   getSecurityLevel()
       ii)访问用户名(该访问用户名称必须在设置USM的时候添加其相应的UsmUser)
       void setSecurityName(OctetString name)
       OctetString   getSecurityName()
       iii)安全模型(支持三种安全模型)
       void setSecurityModel(int model)
       int   getSecurityModel()
       V)权威引擎ID(authoritative engine ID)
       void   setAuthoritativeEngineID(byte[] id)
       byte[] getAuthoritativeEngineID()
   
对于Target的代码大致如下:(摘自Snmp4j的说明文档)

//Community Target
CommunityTarget target = new CommunityTarget();
target.setCommunity(new OctetString("public"));
target.setAddress(targetAddress);
target.setVersion(SnmpConstants.version1);

//User Target
UserTarget target = new UserTarget();
target.setAddress(targetAddress);
target.setRetries(1);
// set timeout to 500 milliseconds -> 2*500ms = 1s total timeout
target.setTimeout(500);
target.setVersion(SnmpConstants.version3);
target.setSecurityLevel(SecurityLevel.AUTH_PRIV);
target.setSecurityName(new OctetString("MD5DES"));


一、pdu

PDU(协议数据单元),用来表示管理站跟代理站点进行通信的数据。包括PDU的类型、传输的数据集合、错误说明等。
    除Snmpv1 Trap外,其他操作类型的PDU大致相同。
    Snmp4j针对Snmp的各个版本,开发了三个有关PDU的类。PDU(针对Snmpv2c)、PDUv1(针对Snmpv1)、ScopedPDU(针对Snmpv3),但三个类除各自特别的一些参数外,都基本相同。所以Snmp4j在设计的时候,将PDU设计成另外两个的超类,使他们能够共享大部分功能。

(1)PDU
    1)pdu类型(Snmpv1支持5种,get、set、getnext、trap、getresponse,Snmpv2c增加了getbulk、inform
    int   getType()
    void   setType(int type)
   
    2)错误(主要有6种错误,请参见RFC)
    void   setErrorStatus(int   status)
    int   getErrorStatus()
    String    getErrorStatusText()
    void   setErrorIndex(int index)
    int   getErrorindex()

    3)变量
    void add(VariableBinding vb) // 对于除get、getnext、getbulk外
    void addAll(VariableBinding[] vbs) // 对于除get、getnext、getbulk外的
    void addOID(VariableBinding vb) // 对于get、getnext、getbulk
    void addAllOID(VariableBinding[] vbs) // 对于get、getnext、getbulk
    VariableBinding get(int i)
    VariableBinding[] toArray()
    Vector   getVariableBindings()
    void set(int i,VariableBinding vb)
    void   remove(int i)
    void   clear()
    int   size()
   
(2)PDUv1
    PDUv1继承了PDU,除Snmp v1 trap操作外 ,其它都跟PDU相同。因为Snmp v1的Trap设计PDU不同于其他操作的PDU,它包括:对象标识符(OID)、代理ip地址(agent-addr)、时间戳(time-stamp)、通用代理类型(generic-trap)、特定代理类型(specific-trap)。所以相应的增加了对这些字段的get/set操作。具体参见API文档。

(3)ScopedPDU
    SnmpV3采用了不同安全策略,它的PDU也增加了有关安全方面的几个字段:上下文名称(ContextName)、上下文引擎ID(contextEngineID)等,其它PDU字段属于USM来管理。
    所以ScopedPDU在PDU的基础上增加了相应的get/set操作。参见API。

示例代码如下:(摘自API文档)

SNMPv1/v2c GETNEXT PDU
import org.snmp4j.PDU;
import org.snmp4j.smi.*;
...
PDU pdu = new PDU();
pdu.add(new VariableBinding(new OID("1.3.6.1.2.1.1.1"))); // sysDescr
pdu.add(new VariableBinding(new OID("1.3.6.1.2.1.2.1"))); // ifNumber
pdu.setType(PDU.GETNEXT);
...SNMPv3 GETBULK PDU
import org.snmp4j.ScopedPDU;
import org.snmp4j.smi.*;
...
ScopedPDU pdu = new ScopedPDU();
pdu.add(new VariableBinding(new OID("1.3.6.1.2.1.2.1"))); // ifNumber
pdu.add(new VariableBinding(new OID("1.3.6.1.2.1.2.2.1.10"))); // ifInOctets
pdu.add(new VariableBinding(new OID("1.3.6.1.2.1.2.2.1.16"))); // ifOutOctets
pdu.setType(PDU.GETBULK);
pdu.setMaxRepetitions(50);
// Get ifNumber only once
pdu.setNonRepeaters(1);
// set context non-default context (default context does not need to be set)
pdu.setContextName(new OctetString("subSystemContextA"));
// set non-default context engine ID (to use targets authoritative engine ID
// use an empty (size == 0) octet string)
pdu.setContextEngineID(OctetString.fromHexString("80:00:13:70:c0:a8:01:0d"));
...SNMPv1 TRAP PDU
import org.snmp4j.PDUv1;
...
PDUv1 pdu = new PDUv1();
pdu.setType(PDU.V1TRAP);
pdu.setGenericTrap(PDUv1.COLDSTART);
...SNMPv2c/SNMPv3 INFORM PDU
import org.snmp4j.ScopedPDU;
...
ScopedPDU pdu = new ScopedPDU();
pdu.setType(PDU.INFORM);
// sysUpTime
long sysUpTime = (System.currentTimeMillis() - startTime) / 10;
pdu.add(new VariableBinding(SnmpConstants.sysUpTime, new TimeTicks(sysUpTime)));
pdu.add(new VariableBinding(SnmpConstants.snmpTrapOID, SnmpConstants.linkDown));
// payload
pdu.add(new VariableBinding(new OID("1.3.6.1.2.1.2.2.1.1"+downIndex),
                            new Integer32(downIndex)));
...

二、snmp

    snmp类是SNMP4J的核心,它提供了发送和接收SNMP PDUs的方法,所有的SNMP PDU 类型都可以采用同步或者异步的方式被发送。

    Snmp采用独立的传输协议,通过TransportMapping 接口调用addTransportMapping(TransportMapping transportMapping) 方法或者采用默认的构造函数来实现传输映射,以此来实现信息的传输。

  下面的代码片段是采用UDP传输方式来实现一个SNMPv3的SNMP session :

Java代码
Address targetAddress = GenericAddress.parse("udp:127.0.0.1/161");  
   TransportMapping transport = new DefaultUdpTransportMapping();  
   snmp = new Snmp(transport);  
   USM usm = new USM(SecurityProtocols.getInstance(),  
                     new OctetString(MPv3.createLocalEngineID()), 0);  
   SecurityModels.getInstance().addSecurityModel(usm);  
   transport.listen(); 

Address targetAddress = GenericAddress.parse("udp:127.0.0.1/161");
   TransportMapping transport = new DefaultUdpTransportMapping();
   snmp = new Snmp(transport);
   USM usm = new USM(SecurityProtocols.getInstance(),
                     new OctetString(MPv3.createLocalEngineID()), 0);
   SecurityModels.getInstance().addSecurityModel(usm);
   transport.listen();
 

如何实现SNMPv3信息的同步发送,下面举例说明:


Java代码
// add user to the USM  
   snmp.getUSM().addUser(new OctetString("MD5DES"),  
                         new UsmUser(new OctetString("MD5DES"),  
                                     AuthMD5.ID,  
                                     new OctetString("MD5DESUserAuthPassword"),  
                                     PrivDES.ID,  
                                     new OctetString("MD5DESUserPrivPassword")));  
   // create the target  
   UserTarget target = new UserTarget();  
   target.setAddress(targetAddress);  
   target.setRetries(1);  
   target.setTimeout(5000);  
   target.setVersion(SnmpConstants.version3);  
   target.setSecurityLevel(SecurityLevel.AUTH_PRIV);  
   target.setSecurityName(new OctetString("MD5DES"));  
 
   // create the PDU  
   PDU pdu = new ScopedPDU();  
   pdu.add(new VariableBinding(new OID("1.3.6")));  
   pdu.setType(PDU.GETNEXT);  
 
   // send the PDU  
   ResponseEvent response = snmp.send(pdu, target);  
   // extract the response PDU (could be null if timed out)  
   PDU responsePDU = response.getResponse();  
   // extract the address used by the agent to send the response:  
   Address peerAddress = response.getPeerAddress();  

 An asynchronous SNMPv1 request is sent by the following code:   
 
   // setting up target  
   CommunityTarget target = new CommunityTarget();  
   target.setCommunity(new OctetString("public"));  
   target.setAddress(targetAddress);  
   target.setRetries(2);  
   target.setTimeout(1500);  
   target.setVersion(SnmpConstants.version1);  
   // creating PDU  
   PDU pdu = new PDU();  
   pdu.add(new VariableBinding(new OID(new int[] {1,3,6,1,2,1,1,1})));  
   pdu.add(new VariableBinding(new OID(new int[] {1,3,6,1,2,1,1,2})));  
   pdu.setType(PDU.GETNEXT);  
   // sending request  
   ResponseListener listener = new ResponseListener() {  
     public void onResponse(ResponseEvent event) {  
       // Always cancel async request when response has been received  
       // otherwise a memory leak is created! Not canceling a request  
       // immediately can be useful when sending a request to a broadcast  
       // address.  
       ((Snmp)event.getSource()).cancel(event.getRequest(), this);  
       System.out.println("Received response PDU is: "+event.getResponse());  
     }  
   };  
   snmp.sendPDU(pdu, target, null, listener);  
   
//Traps (notifications) and other SNMP PDUs can be received by adding the folling code to the first code snippet above:   
   CommandResponder trapPrinter = new CommandResponder() {  
     public synchronized void processPdu(CommandResponderEvent e) {  
       PDU command = e.getPDU();  
       if (command != null) {  
         System.out.println(command.toString());  
       }  
     }  
   };  
   snmp.addCommandResponder(trapPrinter);  
   
 
 
Version:   
1.8   
Author:   
Frank Fock  
translate:avery_leo 

// add user to the USM
   snmp.getUSM().addUser(new OctetString("MD5DES"),
                         new UsmUser(new OctetString("MD5DES"),
                                     AuthMD5.ID,
                                     new OctetString("MD5DESUserAuthPassword"),
                                     PrivDES.ID,
                                     new OctetString("MD5DESUserPrivPassword")));
   // create the target
   UserTarget target = new UserTarget();
   target.setAddress(targetAddress);
   target.setRetries(1);
   target.setTimeout(5000);
   target.setVersion(SnmpConstants.version3);
   target.setSecurityLevel(SecurityLevel.AUTH_PRIV);
   target.setSecurityName(new OctetString("MD5DES"));

   // create the PDU
   PDU pdu = new ScopedPDU();
   pdu.add(new VariableBinding(new OID("1.3.6")));
   pdu.setType(PDU.GETNEXT);

   // send the PDU
   ResponseEvent response = snmp.send(pdu, target);
   // extract the response PDU (could be null if timed out)
   PDU responsePDU = response.getResponse();
   // extract the address used by the agent to send the response:
   Address peerAddress = response.getPeerAddress();
 An asynchronous SNMPv1 request is sent by the following code:

   // setting up target
   CommunityTarget target = new CommunityTarget();
   target.setCommunity(new OctetString("public"));
   target.setAddress(targetAddress);
   target.setRetries(2);
   target.setTimeout(1500);
   target.setVersion(SnmpConstants.version1);
   // creating PDU
   PDU pdu = new PDU();
   pdu.add(new VariableBinding(new OID(new int[] {1,3,6,1,2,1,1,1})));
   pdu.add(new VariableBinding(new OID(new int[] {1,3,6,1,2,1,1,2})));
   pdu.setType(PDU.GETNEXT);
   // sending request
   ResponseListener listener = new ResponseListener() {
     public void onResponse(ResponseEvent event) {
       // Always cancel async request when response has been received
       // otherwise a memory leak is created! Not canceling a request
       // immediately can be useful when sending a request to a broadcast
       // address.
       ((Snmp)event.getSource()).cancel(event.getRequest(), this);
       System.out.println("Received response PDU is: "+event.getResponse());
     }
   };
   snmp.sendPDU(pdu, target, null, listener);
 
//Traps (notifications) and other SNMP PDUs can be received by adding the folling code to the first code snippet above:
   CommandResponder trapPrinter = new CommandResponder() {
     public synchronized void processPdu(CommandResponderEvent e) {
       PDU command = e.getPDU();
       if (command != null) {
         System.out.println(command.toString());
       }
     }
   };
   snmp.addCommandResponder(trapPrinter);
 


Version:
1.8
Author:
Frank Fock
translate:avery_leo

 

Snmp类提供了一套有关Snmp的功能接口。具体来讲,就是发送、接受、创建Snmp消息。
    一个Snmp对象是一个Session,而在Snmp4j中,一个Session可以同多个远程设备通信。

(1) Snmp、Target、PDU三者的关系
    Target代表远程设备或者远程实体、PDU代表管理端同Target通信的数据,Snmp就代表管理者管理功能(其实就是数据的收发)的具体执行者。
       打个比方:Target就是你远方的恋人,PDU就是你们之间传递的情书、而Snmp就是负责帮你寄信收信的邮差。

(2)Snmp收发数据的两种方式
    Snmp可以同步、也可异步收发数据。详细见代码示例说明。

(3)Snmp与传输层协议
    Snmp可以定制传输层协议,一般选择udp,也可以选择tcp。详细见代码示例说明。

(4)Snmp与Usm
    创建Snmp用来发送Snmpv3版本的消息时候,一般还要创建USM,将它添加至安全模型管理器(SecriryModels)中,同时还需要向Usm中添加相应的USM用户(UsmUser)。详细见代码示例说明。

代码示例:(摘自Snmp4j的API文档)

(1)创建Snmp
    1)使用UDP传输协议

TransportMapping transport = new DefaultUdpTransportMapping();
   snmp = new Snmp(transport);     2)使用TCP传输协议

TransportMapping transport = new DefaultTcpTransportMapping();
   snmp = new Snmp(transport);    3)创建用于Snmpv3的Snmp

// 创建Snmp
   TransportMapping transport =
      new DefaultUdpTransportMapping();
   Snmp snmp = new Snmp(transport);
   if (version == SnmpConstants.version3) {
       byte[] localEngineID =
            ((MPv3)snmp.getMessageProcessingModel(MessageProcessingModel.MPv3)).createLocalEngineID();
       // 创建USM
       USM usm = new USM(SecurityProtocols.getInstance(),
                      new OctetString(localEngineID), 0);
       // 将USM添加至安全模式管理器中
       // 安全模型管理器采用了单例模式,它内部可以维护为3个安全模型,分别对应Snmp三个版本
       SecurityModels.getInstance().addSecurityModel(usm);
       snmp.setLocalEngine(localEngineID, 0, 0);
       // 添加用户
       snmp.getUSM().addUser(securityName,new UsmUser(securityName,authProtocol,
                            authPassphrase,privProtocol,privPassphrase));
     }

(2)同步收发消息

import org.snmp4j.*;
    ...
    Snmp snmp = new Snmp(new DefaultUdpTransportMapping());
    ...
    ResponseEvent response = snmp.send(requestPDU, target);
    if (response.getResponse() == null) {
       // request timed out
       ...
    }else {
        System.out.println("Received response from: "+
                       response.getPeerAddress());
        // dump response PDU
        System.out.println(response.getResponse().toString());
    }

(3)异步收发消息

import org.snmp4j.*;
    import org.snmp4j.event.*;
    ...
    Snmp snmp = new Snmp(new DefaultUdpTransportMapping());
    ...
    // 增加监听器
    ResponseListener listener = new ResponseListener() {
       public void onResponse(ResponseEvent event) {
            PDU response = event.getResponse();
            PDU request = event.getRequest();
            if (response == null) {
                System.out.println("Request "+request+" timed out");
            } else {
                System.out.println("Received response "+response+" on request "+
                               request);
            }
      };
     snmp.sendPDU(request, target, null, listener);
     ...(4)实现trap
       实现trap需要三步:
       1)创建Snmp;
       2)对于listen()使处于网络监听(实际上是同于网络编程中的Socket监听);
       3)实现CommandResponder接口的监听器,并且调用Snmp.addCommandResponder(CommandResponder)注册监听器。

import org.snmp4j.*;
    import org.snmp4j.smi.*;
    import org.snmp4j.mp.SnmpConstants;
    ...
    TransportMapping transport =
        new DefaultUdpTransportMapping(new UdpAddress("0.0.0.0/161"));
    Snmp snmp = new Snmp(transport);
    if (version == SnmpConstants.version3) {
        byte[] localEngineID =
           ((MPv3)snmp.getMessageProcessingModel(MessageProcessingModel.MPv3)).createLocalEngineID();
        USM usm = new USM(SecurityProtocols.getInstance(),
                      new OctetString(localEngineID), 0);
        SecurityModels.getInstance().addSecurityModel(usm);
        snmp.setLocalEngine(localEngineID, 0, 0);
        // Add the configured user to the USM
        ...
    }
    // 注册命令响应监听器
    snmp.addCommandResponder(this);
    transport.listen();
    ...
    // 实现CommandResponder接口
    public synchronized void processPdu(CommandResponderEvent e) {
        PDU command = e.getPdu();
        if (command != null) {
        ...
       }
    }
总结
    Snmp内含了一个消息分发器,消息分发器中内含了处理网络的线程,在使用完后最好调用close(),将其资源回归处理。
    掌握了上面所说的三个概念,基本上可以使用Snmp4j编写Snmp的程序了。  
    有关Snmp4j编程最好也最详细的资料:API文档和源代码。关于使用Snmp4j编写Snmp程序的例子,多线程的例子可以参看源代码中:org.snmp4j.test包下的MultiThreadedTrapReceiver.java,完整的例子可以参看 org.snmp4j.tools.console包下的SnmpRequest(一个命令行的Snmp管理器)。
    不过,要想快速和深入掌握Snmp编程,最好的办法一定是先弄懂Snmp协议,这方面的资料最权威的就是RFC协议了。

 类似资料: