SMTP

자바를 이용한 SNMP 데이터 가져오기 - 3 -

bang2001 2013. 7. 25. 17:43

SNMPGet 

지금까지 OpenNMS의 SNMP API를 이용하여 SNMP 에이전트에 접근할 준비는 거의 마쳤다. 이것을 바탕으로 SNMPGet 유틸리티를 작성하면 <리스트 1>과 같다.

<리스트 1> SNMPGet 유틸리티
1 import org.opennms.protocols.snmp.*; 

3 public class SNMPGet implements SnmpHandler 
4 { 
5 private SnmpParameters params; 
6 private SnmpSession session; 

8 public SNMPGet (InetAddress address, String community) 
9 { 
10 // 읽기를 위한 SNMP 커뮤니티 스트링 정보를 포함하는 SnmpParameters 인스턴스 생성 
11 this.params = new SnmpParameters (community); 
12 // SnmpSession 생성 
13 this.session = new SnmpSession (address, params); 
14 // SnmpSession의 기본 Handler 등록 
15 this.session.setDefaultHandler (this); 
16 } 
17 
18 public void snmpInternalError (SnmpSession session, int err, SnmpSyntax pdu) 
19 { 
20 System.out.println ("인터널 에러 발생"); 
21 } 
22 
23 public void snmpReceivedPdu (SnmpSession session, int command, SnmpPduPacket pdu) 
24 { 
25 System.out.println ("데이터를 받았습니다."); 
26 } 
27 
28 public void snmpTimeoutError (SnmpSession session, SnmpSyntax pdu) 
29 { 
30 System.out.println ("Snmp 타임 아웃 "); 
31 } 
32} 

여기에서 아직 다루고 있지 않은 것은 SNMP 에이전트에게 어떻게 요구 메시지를 보내는가에 해당하는 내용이다. 데이터 요구를 위해서는 SnmpSession 클래스의 send 메쏘드를 사용하고 있다. send 메쏘드는 다음과 같이 4가지가 정의되어 있다. 

send (SnmpPduPacket pdu)
send (SnmpPduPacket pdu, SnmpHandler handler)
send (SnmpPduTrap pdu)
send (SnmpPduTrap pdu, SnmpHandler handler)

SnmpPduTrap을 인자로 사용하는 경우에는 이 API를 이용하여 SNMP 에이전트를 구현했을 때나 다른 모니터링 서버로 SNMP 데이터를 전송하는 경우에 필요한 것이며, 어딘가에 있는 SNMP 에이전트에게 데이터를 요구하기 위해서는 SnmpPduPacket을 인자로 사용해야 한다. 그러나 좀더 정확하게 말하면 SnmpPduPacket을 상속받은 SnmpPduRequest를 이용하는 것이다. <리스트 1>에서 만든 SNMPGet을 데이터를 요구할 수 있도록 변경하기 위해 send () 메쏘드를 추가하면 다음과 같다. Send 메쏘드를 구성할 때 OID 값을 요청할 수 있도록 String 타입의 인자를 하나 받도록 하였다.

public void send (String oid)

try 
{ // 데이터를 요구할 OID 정보 생성 
SnmpVarBind[] vblist = {new SnmpVarBind (oid)};
// SNMP Request를 위한 PDU 생성
// SNMP Request 방법은 GET으로 정의
SnmpPduRequest req = new SnmpPduRequest (SnmpPduPacket.GET, vblist);
// SNMP Request에 해당하는 ID를 부여 
req.setRequestId (SnmpPduPacket.nextSequence ()); 

synchronized (session) 

// SNMP 에이전트에 데이터를 요청한다. 
session.send (req); 
// SNMP 데이터를 받을 때까지 대기 상태로 돌입한다. 
session.wait (); 

session.close (); 

catch (InterruptedException ie) 

}
}

주의할 것은 session.send () 메쏘드 호출 후 wait () 메쏘드를 호출하여 대기 상태로 만들어 준 것이다. 따라서 하나 더 추가해야 하는데 SnmpHandler 인터페이스를 구현하면서 생성한 메쏘드 내부에 전부 session 인스턴스의 wait 상태를 해지하는 작업이 필요하다. 그렇지 않으면 메시지를 받든, 에러가 발생하든 상관없이 무한 대기 상태(wait)로 빠지게 된다. 이를 방지하기 위해 snmpInternalError, snmpReceivedPdu, snmpTimeoutError 메쏘드에 모두 다음 내용을 추가해 준다.

synchronized (session)

session.notify ();
}

이렇게 복잡하게 session 인스턴스를 관리하는 이유는 이벤트 처리 방식으로 구성되어서 각 이벤트마다 SnmpHandler의 메쏘드를 호출하게 되어 있기 때문이다. 물론 이것이 직접적인 이유가 되는 것은 아니고 SNMP 데이터 전송이 UDP를 사용한다는데 있다. 데이터 응답이 요구한대로 순차적으로 이루어지는 것이 아니라 어떤 경우에는 약간의 지연시간 후 응답을 하게 되어 여러 개의 응답이 동시에 발생하는 경우도 있기 때문에 이벤트 발생시 SnmpSession의 인스턴스에 동기화 블럭을 첨가하여 데이터의 API 차원의 손실을 방지하기 위함이다. 지금까지 작성한 프로그램에 실행이 가능하도록 main 메쏘드를 첨가한다.

public static void main (String[] args) throws Exception

InetAddress address = InetAddress.getByName (args[0]); 
SNMPGet snmpGet = new SNMPGet (address, args[1]); 
snmpGet.send (args[2]);
}

실행할 때 지금의 프로그램에서는 SMI 파싱이나 MIB 구조에 대한 내용을 포함하지 않으므로 OID에 대한 정확한 값을 입력해야만 데이터를 받아올 수 있게 된다. 또한 snmpget을 구현한 것이므로 snmpwalk에서 사용한 것처럼 OID 일부분을 명시해서도 안되며 반드시 OID의 최종 값까지 입력해야 한다. 시스템의 Description 정보를 요청하는 명령은 다음과 같다.

$ java SNMPGet localhost public .1.3.6.1.2.1.1.1.0
데이터를 받았습니다.

지금은 "데이터를 받았습니다"라는 메시지를 출력하고 있다. 이것은 snmpReceivedPdu 부분에서 인자로 받은 pdu의 처리를 아직 하지 않았기 때문이다. snmpReceivedPdu 메쏘드에서 데이터로 받은 값을 출력할 수 있도록 변경하도록 한다.

public void snmpReceivedPdu (SnmpSession session, int command, SnmpPduPacket pdu) 

System.out.println ("데이터를 받았습니다"); 

SnmpVarBind[] vb = pdu.toVarBindArray (); 

String value = null; 
for (int i = 0; i < vb.length; i++) 

SnmpSyntax snmpValue = vb[i].getValue (); 

switch (snmpValue.typeId ()) 

case ASN1.OCTETSTRING: 
value = new String (((SnmpOctetString) snmpValue).getString ()); 
break; 


System.out.println (value); 

synchronized (session) 

session.notify (); 

}

앞의 코드에서 볼 수 있듯이 snmpReceivedPdu 메쏘드의 인자로 들어온 pdu는 toVarBindArray 메쏘드로 SnmpVarBind 타입의 배열의 값을 가져온 것을 볼 수 있다. 이것은 SnmpSession의 send 메쏘드를 이용하기 위해 만든 SNMPGet의 send 메쏘드 중 SnmpPduRequest의 인스턴스를 만든 부분을 보면 알 수 있다. 해당 OID를 String형으로 받아서 SnmpVarBind의 배열 형태로 SnmpPduRequest를 생성했기 때문에 데이터를 받았을 때도 같은 형태인 것이다. 여기서는 단순한 GET의 타입으로 요청한 것이기 때문에 당연히 길이는 1인 배열만을 반환하게 된다. 

데이터 타입을 파악하기 위해 받아온 배열에서 다시 SnmpSyntax 부분을 getValue 메쏘
드를 이용하여 얻어온 뒤 Switch-Case문으로 정의된 ASNTYPE을 비교하고 있다. 이렇게 비교하여 해당 타입에 맞는 데이터 형으로 캐스팅하면 그 내용을 가져오게 되는 것이다. 여기서는 출력의 내용을 콘솔로 표시하기 위해 편의상 String 인스턴스(변수명 : value)에 그 내용을 담도록 하였다. 이것을 실행하면 실제 Description 내용을 가져올 수 있게 된다. 

$ java SNMPGet ece.uos.ac.kr public .1.3.6.1.2.1.1.1.0
데이터를 받았습니다
Linux ece.uos.ac.kr 2.4.20-xfs #13 SMP Tue Feb 25 07:28:16 Local time zone must be set--see zic i686

하지만 이것은 SNMP 데이터 중 OCTET STRING형에 대한 처리만을 하고 있으므로 switch문 내부에 ASNTYPE을 비교하여 처리하는 내용을 완성해줘야 한다. ASNTYPE에 해당하는 테이블은 다음과 같다. 이 내용은 org.opennms.protocols.snmp.asn1.ASN1에 정의되어 있다.

<표 2> ASNTYPE 테이블
 <표 2> ASNTYPE 테이블
 데이터 형 이름 값
 BITSTRING3
 BOOLEAN  1
 SnmpCounter3265
 SnmpCounter6470
 SnmpEndOfMibView-126
 SnmpGauge3266
 SnmpInt32 2
 SnmpIPAddress64
 SnmpNoSuchInstance-127
 SnmpNoSuchObject-128
 SnmpNull5
 SnmpObjectID6
 SnmpOctetString4
 SnmpOpaque68
 SnmpTimeTicks67
 SnmpUInt3266
 SnmpVarBind48

 

출처 : http://silent1002.blog.me/12426113