一、前言
之前有在弄監控服務器這塊的工作,今天來整體總結下。因為有些服務器(路由器、交換機等都是基於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 }
測試如下: