基於Java使用Snmp4j進行監控與采集(snmptrap、snmpwalk、snmpget)


一、前言

之前有在弄監控服務器這塊的工作,今天來整體總結下。因為有些服務器(路由器、交換機等都是基於snmp協議的)必須使用snmp協議去監控采集和接收信息,所以必須去了解snmp相關內容,以及如何在基於java上開發。關於了解snmp相關內容,必看《SNMP簡單網絡管理協議》這本書里面介紹的很詳細,另外推薦這位前輩的博文寫的很到位 《snmp學習總結》。關於snmp4j的介紹也可以看看前面這位前輩關於 《snmp學習總結》的最后一篇博文《snmp4j介紹》。當然本篇主要記錄如何基於Java如何使用snmp4j去開發實現監控與采集,下面我們直接結合源碼以及實例講解:

二、針對源碼進行分析:

  1、核心對象SNMP的初始化。

  源碼中有四種初始化方法及四個構造函數,其實都大同小異:參數少的就必須后續添加,參數多的必須提前初始化。
       

  我們先看看第一個無參構造函數,源碼很簡單,但是注釋很多,所以看源碼必須要先看注釋。

 1 /**
 2    * Creates a {@code Snmp} instance that uses a
 3    * {@code MessageDispatcherImpl} with no message processing
 4    * models and no security protols (by default). You will have to add
 5    * those by calling the appropriate methods on
 6    * {@link #getMessageDispatcher()}.
 7    * <p>
 8    * At least one transport mapping has to be added before {@link #listen()}
 9    * is called in order to be able to send and receive SNMP messages.
10    * <p>
11    * To initialize a {@code Snmp} instance created with this constructor
12    * follow this sample code:
13    * <pre>
14    * Transport transport = ...;
15    * Snmp snmp = new Snmp();
16    * SecurityProtocols.getInstance().addDefaultProtocols();
17    * MessageDispatcher disp = snmp.getMessageDispatcher();
18    * disp.addMessageProcessingModel(new MPv1());
19    * disp.addMessageProcessingModel(new MPv2c());
20    * snmp.addTransportMapping(transport);
21    * OctetString localEngineID = new OctetString(
22    *    MPv3.createLocalEngineID());
23    *    // For command generators, you may use the following code to avoid
24    *    // engine ID clashes:
25    *    // MPv3.createLocalEngineID(
26    *    //   new OctetString("MyUniqueID"+System.currentTimeMillis())));
27    * USM usm = new USM(SecurityProtocols.getInstance(), localEngineID, 0);
28    * disp.addMessageProcessingModel(new MPv3(usm));
29    * snmp.listen();
30    * </pre>
31    */
32   public Snmp() {
33     this.messageDispatcher = new MessageDispatcherImpl();
34     if (SNMP4JSettings.getSnmp4jStatistics() != SNMP4JSettings.Snmp4jStatistics.none) {
35       counterSupport = CounterSupport.getInstance();
36     }
37   }

  從上面注釋中可以看出要初始化snmp需要設置messageDispatcher里面的參數和TransportMapping參數,如果沒有設置好這個兩個參數,發送報文時會報錯(見下面案例).所以我們可以直接使用第三個構造函數。

  接下來我們來看第二個構造函數Snmp(TransportMapping<? extends Address> transportMapping):

 1 /**
 2    * Creates a <code>Snmp</code> instance that uses a
 3    * <code>MessageDispatcherImpl</code> with all supported message processing
 4    * models and the default security protols for dispatching.
 5    * <p>
 6    * To initialize a <code>Snmp</code> instance created with this constructor
 7    * follow this sample code:
 8    * <pre>
 9    * Transport transport = ...;
10    * Snmp snmp = new Snmp(transport);
11    * OctetString localEngineID =
12    *   new OctetString(snmp.getMPv3().getLocalEngineID());
13    * USM usm = new USM(SecurityProtocols.getInstance(), localEngineID, 0);
14    * SecurityModels.getInstance().addSecurityModel(usm);
15    * snmp.listen();
16    * </pre>
17    *
18    * @param transportMapping TransportMapping
19    *    the initial <code>TransportMapping</code>. You can add more or remove
20    *    the same later.
21    */
22   public Snmp(TransportMapping<? extends Address> transportMapping) {
23     this();
24  initMessageDispatcher();
25     if (transportMapping != null) {
26       addTransportMapping(transportMapping);
27     }
28   }
protected final void initMessageDispatcher() {
    this.messageDispatcher.addCommandResponder(this);
    this.messageDispatcher.addMessageProcessingModel(new MPv2c());
    this.messageDispatcher.addMessageProcessingModel(new MPv1());
    this.messageDispatcher.addMessageProcessingModel(new MPv3());
    SecurityProtocols.getInstance().addDefaultProtocols();
}

  從源碼中可以看到它幫我們設置了messageDispatcher里面的參數,只要我們提供TransportMapping參數即可。第四個構造函數Snmp(MessageDispatcher messageDispatcher)其實跟第一個類似同樣需要提供兩個參數,所以第三個和第四個就列出來了。其中涉及到接口有MessageDispatcher接口、MessageProcessingModel接口,涉及到的類有MPv1、MPv2和MPv3分別對應snmp版本v1、v2c和v3。

/**
 * @description MessageDispatcher接口定義了處理傳入的SNMP消息並將其分派到感興趣的CommandResponder實例的實例的公共服務。它還提供了一個發送出去的SNMP消息的服務。
 */
public interface MessageDispatcher extends TransportListener {}
/**
 * @description MessageProcessingModel 接口為所有SNMP消息處理模型定義了通用方法。
 */
public interface MessageProcessingModel {}
/**
 * @description TransportMapping定義了SNMP傳輸映射的公共接口。傳輸映射只能支持單個傳輸協議。
 */
public interface TransportMapping<A extends Address> {}

  2、核心對象Target

  我們先看下Target對象下的繼承關系。其中主要用到的子對象是CommunityTarget和UserTarget,CommunityTarget用於SNMPv1和SNMPv2c這兩個版本,而UserTarget用於SNMPV3版本。

       

  在初始化CommunityTarget時默認使用的是snmpv1(適用於snmpv2).

/**
   * Default constructor.
   */
  public CommunityTarget() {
    setVersion(SnmpConstants.version1);
    setSecurityLevel(SecurityLevel.NOAUTH_NOPRIV);
    setSecurityModel(SecurityModel.SECURITY_MODEL_SNMPv1);
  }

  3、核心對象PDU

  

  跟Target一樣,針對snmp的不同版本是使用不同的子類去實現。PDUv1用於SNMPv1和SNMPv2c這兩個版本,而ScopedPDU用於SNMPV3版本。

三、案例分析:

  1、snmpGet功能測試:

  第一步:要初始snmp並開啟監聽。其中有點不同的是,為了支持snmpv3版本的處理需要增加用戶並設置安全名稱和加密算法。(關於那些靜態變量的值,最好放到配置文件中顯得靈活點)。

另外再說明下:snmp是基於udp協議發送報文的,且snmp端口默認為161。

 1 public class SnmpUtil {
 2     private static Logger log = LoggerFactory.getLogger(SnmpUtil.class);
 3     public static Snmp snmp = null;
 4     private static String community = "public";
 5     private static String ipAddress = "udp:10.10.112.105/";
 6     
 7     /**
 8      * @description 初始化snmp
 9      * @author YuanFY
10      * @date 2017年12月16日 上午10:28:01
11      * @version 1.0
12      * @throws IOException 
13      */
14     public static void initSnmp() throws IOException{
15             //1、初始化多線程消息轉發類
16             MessageDispatcher messageDispatcher = new MessageDispatcherImpl(); 
17             //其中要增加三種處理模型。如果snmp初始化使用的是Snmp(TransportMapping<? extends Address> transportMapping) ,就不需要增加 
18             messageDispatcher.addMessageProcessingModel(new MPv1());
19             messageDispatcher.addMessageProcessingModel(new MPv2c());
20             //當要支持snmpV3版本時,需要配置user
21             OctetString localEngineID = new OctetString(MPv3.createLocalEngineID());
22             USM usm = new USM(SecurityProtocols.getInstance().addDefaultProtocols(), localEngineID, 0);
23             UsmUser user = new UsmUser(new OctetString("SNMPV3"), AuthSHA.ID, new OctetString("authPassword"), 
24                 PrivAES128.ID, new OctetString("privPassword"));
25             usm.addUser(user.getSecurityName(), user);
26             messageDispatcher.addMessageProcessingModel(new MPv3(usm));
27             //2、創建transportMapping
28             UdpAddress updAddr = (UdpAddress) GenericAddress.parse("udp:10.10.112.177/161");
29             TransportMapping<?> transportMapping = new DefaultUdpTransportMapping(updAddr);
30             //3、正式創建snmp
31             snmp = new Snmp(messageDispatcher, transportMapping);
32             //開啟監聽
33             snmp.listen();
34     }
35 }

  其中要注意的是UdpAddress updAddr = (UdpAddress) GenericAddress.parse("udp:10.10.112.177/161"); 只能指定本機ip,要么不要設置地址。請看DefaultUdpTransportMapping的源碼

 1 /**
 2    * Creates a UDP transport on the specified address. The address will not be
 3    * reused if it is currently in timeout state (TIME_WAIT).
 4    *
 5    * @param udpAddress
 6    *    the local address for sending and receiving of UDP messages.  7    * @throws IOException
 8    *    if socket binding fails.
 9    */
10   public DefaultUdpTransportMapping(UdpAddress udpAddress) throws IOException {
11     super(udpAddress);
12     socket = new DatagramSocket(udpAddress.getPort(),
13                                 udpAddress.getInetAddress());
14   }

  第二步: 根據snmp版本創建Target對象,其中針對snmpV3版本需要設置安全級別和安全名稱,其中安全名稱是創建snmp指定user設置的new OctetString("SNMPV3"),針對snmpv1和snmpv2c需要設置團體名。另外必須設置ipAddress,且對應的主機要配置snmp否則獲取不到值。如下:

 1 private static Target createTarget(int version, int port) {
 2         Target target = null;
 3         if (!(version == SnmpConstants.version3 || version == SnmpConstants.version2c || version == SnmpConstants.version1)) {
 4             log.error("參數version異常");
 5             return target;
 6         }
 7         if (version == SnmpConstants.version3) {
 8             target = new UserTarget();
 9             //snmpV3需要設置安全級別和安全名稱,其中安全名稱是創建snmp指定user設置的new OctetString("SNMPV3")
10             target.setSecurityLevel(SecurityLevel.AUTH_PRIV);
11             target.setSecurityName(new OctetString("SNMPV3"));
12         } else {
13             //snmpV1和snmpV2需要指定團體名名稱
14             target = new CommunityTarget();
15             ((CommunityTarget)target).setCommunity(new OctetString(community));
16             if (version == SnmpConstants.version2c) {
17                 target.setSecurityModel(SecurityModel.SECURITY_MODEL_SNMPv2c);
18             }
19         }
20         target.setVersion(version);
21         //必須指定,沒有設置就會報錯。
22         target.setAddress(GenericAddress.parse(ipAddress+port));
23         target.setRetries(5);
24         target.setTimeout(3000);
25         return target;
26     }

  第三步:創建報文。其中要注意的是pdu可以設置類型,如果想要用snmpget方法,就設置PDU.GET.

 1     private static PDU createPDU(int version, int type, String oid){
 2         PDU pdu = null;
 3         if (version == SnmpConstants.version3) {
 4             pdu = new ScopedPDU();
 5         }else {
 6             pdu = new PDUv1();
 7         }
 8         pdu.setType(type);
 9         //可以添加多個變量oid
10         pdu.add(new VariableBinding(new OID(oid)));
11         return pdu;
12     }

  最后一步發送報文也是最重要的一步,需要前面三步的支撐才能進行。如下:

 1 public static void snmpGet(String oid){
 2         try {
 3             //1、初始化snmp,並開啟監聽
 4             initSnmp();
 5             //2、創建目標對象
 6             Target target = createTarget(SnmpConstants.version2c, SnmpConstants.DEFAULT_COMMAND_RESPONDER_PORT);
 7             //3、創建報文
 8             PDU pdu = createPDU(SnmpConstants.version2c, PDU.GET, oid);
 9             System.out.println("-------> 發送PDU <-------"); 
10             //4、發送報文,並獲取返回結果
11             ResponseEvent responseEvent = snmp.send(pdu, target);
12             PDU response = responseEvent.getResponse();
13             System.out.println("返回結果:" + response);
14         }
15         catch (IOException e) {
16             e.printStackTrace();
17         }
18     }

  測試如下:

    public static void main(String[] args) {
        snmpGet("1.3.6.1.2.1.1.1.0");
    }

  output

-------> 發送PDU <-------
返回結果:RESPONSE[requestID=1344419162, errorStatus=Success(0), errorIndex=0, VBS[1.3.6.1.2.1.1.1.0 = Linux localhost.localdomain 3.10.0-327.36.2.el7.x86_64 #1 SMP Mon Oct 10 23:08:37 UTC 2016 x86_64]]

  從中可以得知,snmpget是可以根據指定的oid獲取其對應的內容的。

  2、SNMPWalk功能測試

  查看了下PDU的源碼,發現沒有對應snmpwalk的類型,所以使用getNext類型來實現snmpwalk功能。
  

 1 public static void snmpWalk(String oid) {
 2         try {
 3             //1、初始化snmp,並開啟監聽
 4             initSnmp();
 5             //2、創建目標對象
 6             Target target = createTarget(SnmpConstants.version2c, SnmpConstants.DEFAULT_COMMAND_RESPONDER_PORT);
 7             //3、創建報文
 8             PDU pdu = createPDU(SnmpConstants.version2c, PDU.GETNEXT, oid);
 9             System.out.println("-------> 發送PDU <-------"); 
10             //4、發送報文,並獲取返回結果
11             boolean matched = true;
12             while (matched) {
13                 ResponseEvent responseEvent = snmp.send(pdu, target);
14                 if (responseEvent == null || responseEvent.getResponse() == null) {
15                     break;
16                 }
17                 PDU response = responseEvent.getResponse();
18                 String nextOid = null;
19                 Vector<? extends VariableBinding> variableBindings = response.getVariableBindings();
20                 for (int i = 0; i < variableBindings.size(); i++) {
21                     VariableBinding variableBinding = variableBindings.elementAt(i);
22                     Variable variable = variableBinding.getVariable();
23                     nextOid = variableBinding.getOid().toDottedString();
24                     //如果不是這個節點下的oid則終止遍歷,否則會輸出很多,直到整個遍歷完。
25                     if (!nextOid.startsWith(oid)) {
26                         matched = false;
27                         break;
28                     }
29                     //System.out.println(variable);
30                 }
31                 if (!matched) {
32                     break;
33                 }
34                 pdu.clear();
35                 pdu.add(new VariableBinding(new OID(nextOid)));
36                 System.out.println("返回結果:" + response);
37             }
38         }
39         catch (IOException e) {
40             // TODO Auto-generated catch block
41             e.printStackTrace();
42         }
43     }

  測試如下:

1     public static void main(String[] args) {
2         //snmpGet("1.3.6.1.2.1.1.1.0");
3         snmpWalk("1.3.6.1.2.1.25.3.3.1.2");//CPU的當前負載,N個核就有N個負載4     }
-------> 發送PDU <-------

返回結果:RESPONSE[requestID=1014693266, errorStatus=Success(0), errorIndex=0, VBS[1.3.6.1.2.1.25.3.3.1.2.196608 = 1]]
返回結果:RESPONSE[requestID=1014693268, errorStatus=Success(0), errorIndex=0, VBS[1.3.6.1.2.1.25.3.3.1.2.196609 = 0]]

  用命令獲取的結果是跟代碼輸出的結果是一樣的,如下:

 

  3、前面兩個案例都是跟采集有關,接下來介紹如何監控接收服務器發過來的故障然后提示個用戶,這就需要用到snmptrap了。接下來我們直接看案例:

  處理流程:

  1、必須實現CommandResponder接口

  2、初始化snmp並開啟監聽。這步跟上面初始化一樣,只是面對並發的情況使用MultiThreadedMessageDispatcher進行信息處理。

  3、將當前實現CommandResponder的對象添加至snmp的addCommandResponder才能接收到信息。

  4、處理接收到信息,通知用戶。

 1 package com.yuanfy.study.snmp;
 2 
 3 import java.io.IOException;
 4 
 5 import org.slf4j.Logger;
 6 import org.slf4j.LoggerFactory;
 7 import org.snmp4j.CommandResponder;
 8 import org.snmp4j.CommandResponderEvent;
 9 import org.snmp4j.MessageDispatcher;
10 import org.snmp4j.MessageDispatcherImpl;
11 import org.snmp4j.PDU;
12 import org.snmp4j.Snmp;
13 import org.snmp4j.TransportMapping;
14 import org.snmp4j.mp.MPv1;
15 import org.snmp4j.mp.MPv2c;
16 import org.snmp4j.mp.MPv3;
17 import org.snmp4j.security.AuthSHA;
18 import org.snmp4j.security.PrivAES128;
19 import org.snmp4j.security.SecurityProtocols;
20 import org.snmp4j.security.USM;
21 import org.snmp4j.security.UsmUser;
22 import org.snmp4j.smi.GenericAddress;
23 import org.snmp4j.smi.OctetString;
24 import org.snmp4j.smi.UdpAddress;
25 import org.snmp4j.transport.DefaultUdpTransportMapping;
26 import org.snmp4j.util.MultiThreadedMessageDispatcher;
27 import org.snmp4j.util.ThreadPool;
28 
29 public class SnmpTrapHandler implements CommandResponder{
30     private static Logger log = LoggerFactory.getLogger(SnmpTrapHandler.class);
31     private static int threadNum = 200;
32     private static String ipAddress = "udp:10.10.112.177/162";
33     private Snmp snmp = null;
34     public void init(){
35         //1、初始化多線程消息轉發類
36         ThreadPool threadPool = ThreadPool.create("SnmpTrap", threadNum);
37         MessageDispatcher messageDispatcher = new MultiThreadedMessageDispatcher(threadPool, new MessageDispatcherImpl()); 
38         //其中要增加三種處理模型。如果snmp初始化使用的是Snmp(TransportMapping<? extends Address> transportMapping) ,就不需要增加 
39         messageDispatcher.addMessageProcessingModel(new MPv1());
40         messageDispatcher.addMessageProcessingModel(new MPv2c());
41         OctetString localEngineID = new OctetString(MPv3.createLocalEngineID());
42         USM usm = new USM(SecurityProtocols.getInstance().addDefaultProtocols(), localEngineID, 0);
43         UsmUser user = new UsmUser(new OctetString("SNMPV3"), AuthSHA.ID, new OctetString("authPassword"), 
44             PrivAES128.ID, new OctetString("privPassword"));
45         usm.addUser(user.getSecurityName(), user);
46         messageDispatcher.addMessageProcessingModel(new MPv3(usm));
47         //2、創建transportMapping
48         TransportMapping<?> transportMapping = null;
49         try {
50             UdpAddress updAddr = (UdpAddress) GenericAddress.parse(System.getProperty("snmp4j.listenAddress", ipAddress));
51             transportMapping = new DefaultUdpTransportMapping(updAddr);
52             //3、正式創建snmp
53             snmp = new Snmp(messageDispatcher, transportMapping);
54             //開啟監聽
55             snmp.listen();
56         } catch (IOException e) { 
57             log.error("初始化transportMapping失敗:", e.getMessage());
58             e.printStackTrace();
59         }
60     }
61     
62     public void start() {
63         init();
64         //一定要將當前對象添加至commandResponderListeners中
65         snmp.addCommandResponder(this);
66         System.out.println("開始監聽trap信息:");
67     }
68     /**
69      * 處理信息方法
70      */
71     @Override
72     public void processPdu(CommandResponderEvent event) {
73         String version = null ;
74         String community = null;
75         if (event.getPDU().getType() == PDU.V1TRAP) {
76             version = "v1";
77             community = new String(event.getSecurityName());
78         } else if (event.getPDU().getType() == PDU.TRAP){
79             if (event.getSecurityModel() == 2) {
80                 version = "v2";
81                 community = new String(event.getSecurityName());
82             }else {
83                 version = "v3";
84             }
85         }
86         System.out.println("接收到的trap信息:[發送來源="+event.getPeerAddress()+",snmp版本="+version+",團體名="+community+", 攜帶的變量="+event.getPDU().getVariableBindings()+"]");
87     }
88     public static void main(String[] args) {
89         SnmpTrapHandler handler = new SnmpTrapHandler();
90         handler.start();
91     }
92 }

  測試如下:

 

 

  

   


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM