Java

자바를 이용한 SNMP

_침묵_ 2006. 4. 17. 23:34
우리가 쉽게 접할 수 있는 SNMP(Simple Network Management Protocol)를 이용한 모니터링 프로그램은 MRTG이다. MRTG에서 SNMP의 데이터를 가져오는 부분은 Perl로 작성되어 있으며 이 데이터를 GD 라이브러리를 사용하여 그래프로 표현해 주는 것이다.

모니터링을 위해 사용한다는 SNMP는 라우터 같은 네트워크 장비나 일반 서버들의 상태를 관리하기 위해 만들어진 프로토콜이다. 물론 장비에 국한하지 않고 대형 응용 프로그램의 관리에도 SNMP를 사용하고 있다. SNMP는 크게 4가지로 구성되어 있다.

◆ SMI(Structure of Management Information)
◆ MIB(Management Information Base)
◆ 프로토콜
◆ 보안과 운영(security and administration)

SMI, MIB 부분은 SNMP를 이용하여 데이터를 교환할 때 어떠한 데이터를 어떠한 형태로 누가 정의했는가에 대한 내용을 담고 있다. 프로토콜은 실제 데이터를 어떻게 요청하고 응답하는지에 대한 내용이며, 보안과 운영은 SNMP에 접근하는 사용자, 패킷에 대한 암호화 같은 부분이 된다. 여기서는 SNMP 에이전트에서 데이터를 가져오는 부분을 자바로 어떻게 구현하는가에 대한 것으로 프로토콜에 대한 부분이다.

자바 SNMP API
가장 쉽게 구할 수 있는 자바 SNMP API는 대부분이 상용 제품이다. 몇 년 전까지만 해도 오픈 프로젝트는 찾아보기 힘들었지만 최근에는 OpenNMS(http://www.opennms.org), NetSNMPj(http://netsnmpj.sourceforge.org) 같은 프로젝트들이 활발히 진행 중에 있다.

OpenNMS의 경우에는 이벤트 핸들링을 하기 좋은 방식으로 구성되어 있으며, Net-SNMPj의 경우 net-snmp의 C API를 JNI(Java Native Interface)를 이용하여 접근하는 형태로 작성됐기 때문에 C를 이용한 SNMP 클라이언트를 작성하던 프로그래머들에게 좀더 친숙한 API이다.

SNMP API를 이용한 프로그램을 테스트하려면 우선 SNMP 에이전트가 설치된 서버가 있어야 한다. SNMP 에이전트는 http://net-snmp.sourceforge.net에서 구할 수 있다. 주의할 것은 netsnmpj API를 사용하려면 Net-SNMP는 5.05 이상을 설치해야 한다는 것이다. Net-SNMP를 설치하면 셸 프롬프트 상에서 SNMP 데이터를 가져오거나(GET) 설정(SET)할 수 있다.

<표 1> SNMP 메시지 타입
 <1> SNMP메시지 타입
 메시지 타입  설명
 GetRequest  단일 항목 요청
 GetNextRequest  현재 요청된 값의 다음 항목을 요청
 GetBulkRequest  여래 개의OID를 이용하여 데이터를 가져오기 위한 메시지
 SetRequest  데이터를SNMP에이전트로 보내주기 위한 메시지 타입
 Response  GET에 대한 응답으로 SNMP에이전트에서 클라이언트로 전송할 때 사용되는 메시지
 Trap  SNMP에이전트 자신의 상태를 다른 장비나 관리자에게 보내주기 위해 사용되는 메시지



SNMP 데이터를 가져올 수 있는 셸 명령어는 snmpget과 snmpwalk가 있다. API 관점에서 본다면 snmpget은 <표1>에서 GetRequest를 사용하는 것이고, snmpwalk는 GetRequest를 이용하여 가져온 데이터를 GetNextRequest로 데이터가 존재하지 않을 때까지 루프를 계속하는 것이다. 각 셸 명령어 실행 결과는 다음과 같다.

$ snmpget -v2c -c public localhost sysDescr.0
SNMPv2-MIB::sysDescr.0 = STRING: 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

Snmpget의 결과는 단일 항목에 대한 데이터를 가져오게 된다.

$ snmpwalk -v2c -c public localhost .1
SNMPv2-MIB::sysDescr.0 = STRING: 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
SNMPv2-MIB::sysObjectID.0 = OID: NET-SNMP-MIB::netSnmpAgentOIDs.10
SNMPv2-MIB::sysUpTime.0 = Timeticks: (101583525) 11 days, 18:10:35.25
SNMPv2-MIB::sysContact.0 = STRING: jsKim
...중략...

반면에 snmpwalk의 결과는 .1로 시작하는 OID 이하의 서브 노드들의 데이터를 계속해서 보여주게 된다.

 

 

---------------------------------------------------------------------------------------

OpenNMS를 이용한 SNMP GET 구현
API의 명칭을 OpenNMS라고 하였지만 사실은 OpenNMS는 하나의 NMS 프로그램이다. 여기서 사용하는 것은 OpenNMS를 구현할 때 사용한 SNMP API 부분만을 이용하는 것이다. SNMP API는 joeSNMP라는 이름으로 제공되고 있다.

http://www.opennms.org/files/releases/joeSNMP에서 다운받을 수 있다.
전체 패키지는 org.opennms.protocols로 구성되어 있으며 org.opennms.test에는 SNMP walk나 trap을 테스트해 볼 수 있는 테스트용 응용 프로그램이 있다. OpenNMS의 SNMP API를 이용하여 데이터를 받는 순서는 <그림 1>과 같다.

사용자 삽입 이미지


<그림 1> SNMP API를 이용해 데이터를 받는 순서




SNMP 에이전트와 통신을 담당하는 클래스는 SnmpSession이며, 에이전트로부터 받은 메시지는 SnmpSession에 의해서 SnmpHandler로 전하게 된다. SnmpHandler는 단순한 인터페이스이므로 이것을 구현하여 SnmpSession에 등록해야 한다. SnmpSession을 생성할 때는 SNMP 커뮤니티 스트링 정보와 SNMP 에이전트의 IP 주소와 같은 정보를 입력해 줘야 한다.

// SnmpSession 생성자
SnmpSession (InetAddress peer)
SnmpSession (SnmpPeer peer)
SnmpSession (InetAddress peer, SnmpParameters params)

SnmpSession 생성자에서 peer라는 이름은 SNMP 에이전트를 가리키며 접속에 필요한 정보들은 SnmpParameters 클래스에서 담당하게 된다.

// SnmpParameters 생성자
SnmpParameters ()
SnmpParameters (int version)
SnmpParameters (Strig readCommunity)
SnmpParameters (String readCommunity, String writeCommunity)

SnmpParameters 클래스에는 SnmpSession에서 SNMP 에이전트와의 통신에 사용할 SNMP 버전 정보, 읽기 커뮤니티 스트링, 쓰기 커뮤니티 스트링에 대한 정보를 담당하게 된다. SnmpSession 클래스의 인스턴스는 다음의 코드로 생성할 수 있다.

SnmpParameters params = new SnmpParameters ("public");
SnmpSession session = new SnmpSession (InetAddress.getByName ("localhost"), params)

그러나 인스턴스인 session은 아직 사용할 준비가 끝난 것은 아니다. SNMP 메시지를 보내기 위해서는 session에 SnmpHandler를 구현한 클래스의 인스턴스를 DefaultHandler로 등록해야 한다. 그렇지 않은 경우 SnmpSession 클래스의 정의에 따라 SnmpHandlerNotDefinedException을 발생하게 된다. SnmpHandler는 인터페이스로 다음과 같이 3개의 메쏘드를 정의하고 있다.

snmpInternalError (SnmpSession session, int err, SnmpSyntax pdu)
snmpReceivedPdu (SnmpSession session, int command, SnmpPduPacket pdu)
snmpTimeoutError (SnmpSession session, SnmpSyntax pdu)

각 메쏘드에서 인자로 받고 있는 session은 메시지를 전송하기 위해 사용했던 SnmpSession을 가리키고 있으며, InternalError의 경우 잘못된 OID나 잘못된 데이터 타입으로 인하여 API 내부 처리시에 에러가 발생한 경우 호출된다. TimeoutError는 SNMP 에이전트에 SnmpSession을 이용하여 데이터를 보냈지만 기본적으로 설정된 타임아웃 시간 내에 응답받지 못하는 경우 호출되도록 되어 있다. receivedPdu는 정상적으로 SNMP 에이전트로부터 데이터를 받은 경우 호출되며 이때 인자인 pdu에는 해당 데이터가 담겨져 있다.

SNMP 에이전트와 통신에 사용되는 데이터 타입들은 모두 SnmpSyntax 인터페이스를 구현하고 있으며, 각 타입은 ASNTYPE으로 정의된 바이트 타입의 필드에 의해 구분되고 있다. 즉, SnmpHandler의 snmpReceivedPdu 메쏘드가 호출되어 받은 SnmpPduPacket은 SnmpSyntax를 구현하고 있는 인스턴스이며, 이 인스턴스 내의 ASNTYPE으로 정의된 필드를 각 SNMP 데이터 타입과 비교한 뒤 캐스팅을 이용하여 사용하도록 하고 있다.

 

----------------------------------------------------------------------------------------

 

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

<리스트 1> SNMPGet 유틸리티
1 import org.opennms.protocols.snmp.*;
2
3 public class SNMPGet implements SnmpHandler
4 {
5 private SnmpParameters params;
6 private SnmpSession session;
7
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 테이블
 데이터 형 이름  값
 BITSTRING 3
 BOOLEAN   1
 SnmpCounter32 65
 SnmpCounter64 70
 SnmpEndOfMibView -126
 SnmpGauge32 66
 SnmpInt32  2
 SnmpIPAddress 64
 SnmpNoSuchInstance -127
 SnmpNoSuchObject -128
 SnmpNull 5
 SnmpObjectID 6
 SnmpOctetString 4
 SnmpOpaque 68
 SnmpTimeTicks 67
 SnmpUInt32 66
 SnmpVarBind 48

 

---------------------------------------------------------------------------------------

 

SNMPWalk
지금까지 작성한 것은 SNMPGet으로 단일 OID에 대한 내용이었다. 그러나 실제 유용하게 사용할 수 있는 것은 snmpwalk와 같은 기능을 하는 것이다. 즉 해당 OID 값을 가지고 그 이하 OID에 대한 정보를 가져오는 것이 SMI 파싱 정보를 사용하지 않는 지금과 같은 프로그램에서는 더 유용하게 된다.

SNMPWalk를 구현하기 위해서는 SNMPGet에서 send 메쏘드와 snmpReceivedPdu 메쏘드의 내용을 변경하는 것으로 가능하다. 먼저 요청하게 되는 send 메쏘드를 변경하면 다음과 같다.

public void send (String oid)
{
try
{
// 데이터를 요구할 OID 정보 생성
SnmpObjectId id = new SnmpObjectId (oid);
int[] ids = id.getIdentifiers (); ++ids[ids.length - 1];
id.setIdentifiers (ids);
stopPoint = id ;

SnmpVarBind[] vblist = {new SnmpVarBind (oid)};
// SNMP Request를 위한 PDU 생성
// SNMP Request 방법은 GETNEXT로 정의
SnmpPduRequest req = new SnmpPduRequest (SnmpPduPacket.GETNEXT, vblist);
// SNMP Request에 해당하는 ID를 부여
req.setRequestId (SnmpPduPacket.nextSequence ());

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

SNMPGet에서와 달라진 것이라면 SnmpPduRequest를 생성하는데 있어 GET 방식이 아닌 GETNEXT 방식으로 한다는 점과 데이터를 요구하는 OID의 생성에서 stopPoint를 지정한 것이다. 이 부분을 제외하고 나머지 부분은 SNMPGet과 SNMPWalk가 동일하다.

데이터를 요청하는 것과는 다르게 데이터를 받는 부분에서는 많은 변화가 생기게 된다. snmpReceivedPdu 메쏘드는 아예 새로 작성되어야 한다. 대량의 데이터가 전송되므로 약간의 에러 검사 코드도 추가되어야 한다. 또한 OID 값이 더 이상 확장될 수 있는지 없는지를 검사하기 위하여 마지막 포인트의 OID를 저장할 수 있게 하나의 필드를 선언해야 한다.

private SnmpObjectId stopPoint = null;

앞의 코드에서 stopPoint를 지정한 부분은 OID 스트링에서 int형의 배열을 추출한 후 마지막에서 두 번째 인자의 값을 하나 증가시켜서 만들고 있다. 즉 SNMPWalk를 구동하여 .1.3.6.1이라는 OID 값을 입력하였다면 이것은 int 형의 배열로 변환되는데 다음과 같이 변환된다.

int ids = {1, 3, 6, 1};

각 int 형 배열에는 .(dot)로 구분된 숫자가 저장되는 것이다. 여기서 총 길이의 -1의 방에 들어 있는 값은 6이 되고 이것을 1 증가시킨 값을 stopPoint로 잡는 것이다. 다시 말해 {1, 3, 7}의 배열이 되고 이것을 OID 스트링으로 변환하면 .1.3.7이 되는 것이다. 이 SNMPWalk 프로그램은 .1.3.6 이하의 데이터를 계속 요구하다가 GETNEXT에 의해 .1.3.7로 시작하는 데이터가 전송되면 중지되는 것이다. 이제 snmpReceivedPdu의 내용을 살펴보자. 내용이 길기 때문에 분할하여 살펴보면 다음과 같다.

public void snmpReceivedPdu (SnmpSession session, int command, SnmpPduPacket pdu)
{
SnmpPduRequest req = null;
if(pdu instanceof SnmpPduRequest)
{
req = (SnmpPduRequest)pdu;
}

if(pdu.getCommand() != SnmpPduPacket.RESPONSE)
{
synchronized(session)
{
session.notify();
}
return;
}
if(req.getErrorStatus() != 0)
{
synchronized(session)
{
session.notify();
}
return;
}

여기까지는 SNMP 에이전트로 받은 데이터가 온전한 형태인지를 검사하는 부분이다. 먼저 SnmpPduRequest의 인스턴스인지 확인한 후 맞는 경우에 캐스팅하여 사용할 수 있도록 한다.

pdu의 command 타입이 RESPONSE가 아닌 경우에는 아무런 처리를 하지 않고 return하도록 명시했는데 이것은 단순한 SNMPWalk만을 구현할 때는 생략해도 좋은 코드이다. 다만 어떠한 SNMP 에이전트에서 우연히 SNMPWalk를 구동한 시스템으로 Trap 메시지를 전송할 경우 Trap 메시지와 SNMP 응답 메시지를 구분하기 위해 삽입한 코드이다. 마지막 부분은 코드의 메쏘드에서 알 수 있듯이 에러가 있었는지를 검사하는 것이다.

SnmpVarBind[] vb = pdu.toVarBindArray ();

InetAddress address = session.getPeer ().getPeer ();

for (int i = 0; i < vb.length; i++)
{
SnmpSyntax snmpValue = vb[i].getValue ();
String value = null;

if (((stopPoint != null) && (stopPoint.compare (vb[i].getName ()) < 0)) || (vb[i].getValue ().typeId () == SnmpEndOfMibView.ASNTYPE))
{
synchronized (session)
{
session.notify ();
}
continue;
}

SNMPGet에서와 마찬가지로 pdu 데이터를 SnmpVarBind 배열로 가져온다. 두 번째 라인에서 peer의 InetAddress를 가져오게 되는데 이것은 지금 처리한 데이터의 OID를 이용하여 다음 OID를 요청할 때 사용하기 위한 것이다.

주의깊게 살펴봐야 하는 부분은 stopPoint를 사용한 if절이다. stopPoint는 앞에서 필드로 정의한 부분이다. stopPoint를 null과 비교한 것은 NullPointerException을 방지하기 위한 것이고 stopPoint.compare (vb[i].getName ())은 현재 데이터를 받은 OID 값과 stopPoint를 비교한 것이다. 여기서 사용한 compare 메쏘드는 두 값이 같은 경우 0을 반환하며 stopPoint가 vb[i].getName ()보다 작은 경우 음수를 리턴하고 큰 경우 양수를 리턴한다. 즉, 음수가 되는 경우는 stopPoint보다 데이터를 받은 OID가 큰 경우이므로 중지하게 된다. 또한 현재 데이터가 마지막 지점이라는 내용을 가지고 있다면 종료하게 된다.

switch (snmpValue.typeId ())
{
case ASN1.INTEGER:
value = Integer.toString (((SnmpInt32) snmpValue).getValue ());
break;

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

if (value != null)
{
System.out.println (vb[i].getName () + " : " + value);
}

데이터를 출력하는 부분이다. 이 코드에서는 정수형(32비트) 데이터를 처리하는 코드를 넣어 보았다. 물론 더 모든 데이터 타입에 대한 처리 코드를 넣어야 온전한 SNMPWalk가 될 수 있다.

try
{
SnmpVarBind[] vblist = { new SnmpVarBind (vb[i].getName ()) };

SnmpPduRequest newReq = new SnmpPduRequest(SnmpPduPacket.GETNEXT, vblist);
newReq.setRequestId(SnmpPduPacket.nextSequence());
session.send(newReq);
}
catch (Exception e)
{
}
}
}

메쏘드의 마지막 부분이다. 단순히 session.notify ()로 끝나지 않았음을 알 수 있다. 현재의 OID를 가지고 다시 GETNEXT 메시지를 이용하여 요청하고 있다. 즉 앞의 조건에 의하여 끝나지 않는 한 계속 SNMP 데이터를 요청하게 되는 것이다.

Net-SNMPi API도 사용해 보기를
지금까지 소개한 두 개의 SNMP API 중 OpenNMS의 API만을 다루었다. 또한 필자가 실제 모니터링 시스템을 개발하는 프로젝트에 사용했던 API 또한 OpenNMS이다. 그러나 개인적으로 더 호감이 가는 것은 여기서 다루지 않은 Net-SNMPj API이다.

프로젝트를 진행할 당시 Net-SNMPj는 자바 쓰레드에서 문제를 일으켰기 때문에 사용할 수가 없었다. 얼마 전까지 만해도 이 문제는 해결되지 않았지만 몇 주 전에 이 문제를 해결한 개발 버전이 발표되었다. Net-SNMPj는 이미 net-snmp로 안정성이 입증된 C API 기반이기에 상당한 잠재력을 가지고 있고 OpenNMS에서 제공하고 있지 않는 SMI 파싱 기능을 포함하고 있다.

어떠한 API를 선택하는지는 프로그래머의 선호도에 달려있다고 본다. 고가의 SNMP API를 사용하지 않고 오픈 프로젝트로 진행된 여러 SNMP API의 사용으로 인하여 좀더 쉽게 SNMP에 접근하고 그 내부에 구현된 부분을 볼 수 있었던 것이 엄청난 수확이 아니었나 생각된다.

'Java' 카테고리의 다른 글

SocketException "Connection time out" 왜 나는 걸까..  (0) 2006.04.24
JAVA와 C 사용(JNI)시 데이터형  (0) 2006.04.07
JNI(Java Native Interface) Part I  (0) 2006.03.24