最近项目中用到了snmp4j包进行设备拓扑,设备性能监控,本文主要讲解一下SNMP,snmp4j包在Java中的使用。
一、SNMP介绍
SNMP是简单网络管理协议,专门设计用于在 IP 网络管理网络节点(服务器、工作站、路由器、交换机及HUBS等)的一种标准协议,它是一种应用层协议。 SNMP 使网络管理员能够管理网络效能,发现并解决网络问题以及规划网络增长。通过 SNMP 接收随机消息(及事件报告)网络管理系统获知网络出现问题。目前, SNMP 有 3 种: SNMPV1 、 SNMPV2 、 SNMPV3。第 1 版和第 2 版没有太大差距,但 SNMPV2 是增强版本,包含了其它协议操作。与前两种相比, SNMPV3 则包含更多安全和远程配置。
1、基本操作类型
SNMP对外提供了三种用于控制MIB对象的基本操作命令。它们是:Get、Set 和 Trap。Get:管理站读取代理者处对象的值。它是SNMP协议中使用率最高的一个命令,因为该命令是从网络设备中获得管理信息的基本方式。Set:管理站设置代理者处对象的值。Trap: 代理者主动向管理站通报重要事件。Trap 消息可以用来通知管理站线路的故障、连接的终端和恢复、认证失败等消息,管理站可相应的作出处理。
2、snmp消息组成
一条snmp消息由版本识别符、团体名、PDU组成。版本识别符用于说明现在使用的是哪个版本的SNMP协议,确保SNMP代理使用相同的协议,每个SNMP代理都直接抛弃与自己协议版本不同的数据报。团体名是基本的安全机制,用于实现SNMP网络管理员访问SNMP管理代理时的身份验证。PDU (协议数据单元)是SNMP消息中的数据区, 即Snmp通信时报文数据的载体。PDU指明了SNMP的消息类型及其相关参数。
3、MIB(信息管理库)
上文提到了MIB对象,MIB是信息管理库,可以理解成为agent维护的管理对象数据库, MIB数据对象以一种树状分层结构进行组织,这个树状结构中的每个分支都有一个专用的名字和一个数字形式的标识符,可以通过其数字标识符来查找MIB中的数据对象,这个数字标识符号从结构树的顶部(或根部)开始,直到各个叶 子节点(即数据对象)为止。
4、OID
每个管理对象都有自己的OID(Object Identifier),管理对象通过树状结构进行组织,OID由树上的一系列整数组成,也就是从根节点 通向它的路径,整数之间用点( . )分隔开,树的叶子节点才是真正能够被管理的对象。
二、Java实现SNMP
1、搭建环境
首先本地计算机和被管理的设备要开启snmp,然后下载snmp4j包,或者在maven项目pom文件添加依赖。
2、代码实现
import org.snmp4j.*;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.mp.MPv1;
import org.snmp4j.mp.MPv2c;
import org.snmp4j.mp.MPv3;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.security.*;
import org.snmp4j.smi.*;
import org.snmp4j.transport.DefaultTcpTransportMapping;
import org.snmp4j.transport.DefaultUdpTransportMapping;
import org.snmp4j.util.MultiThreadedMessageDispatcher;
import org.snmp4j.util.ThreadPool;
import java.io.IOException;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.*;
public class SNMPUtil {
private String address="udp:192.160.0.2/161";
private String username="admin";
private String authpassword="123";
private String privpassword="123";
private Snmp snmp;
/**
* 初始化snmp
* @throws IOException
*/
public void initSnmp() throws IOException {
//1、初始化多线程消息转发类
MessageDispatcher messageDispatcher = new MessageDispatcherImpl();
//其中要增加三种处理模型。如果snmp初始化使用的是Snmp(TransportMapping<? extends Address> transportMapping) ,就不需要增加
messageDispatcher.addMessageProcessingModel(new MPv1());
messageDispatcher.addMessageProcessingModel(new MPv2c());
//当要支持snmpV3版本时,需要配置user
OctetString localEngineID=new OctetString(MPv3.createLocalEngineID());
USM usm=new USM(SecurityProtocols.getInstance().addDefaultProtocols(),localEngineID,0);
OctetString userName= new OctetString(username);
OctetString authpass= new OctetString(authpassword);
OctetString privpass= new OctetString(privpassword);
UsmUser user= new UsmUser(userName,AuthMD5.ID,authpass,PrivDES.ID,privpass);
usm.addUser(user.getSecurityName(),user);
messageDispatcher.addMessageProcessingModel(new MPv3(usm));
TransportMapping transportMapping= new DefaultUdpTransportMapping();
snmp = new Snmp(messageDispatcher,transportMapping);
snmp.listen();
}
/**
* 创建目标对象
* @param oid
* @return
*/
public Target createTarget(String oid){
Target target=null;
int version=1;
if(!(version==SnmpConstants.version1||version==SnmpConstants.version2c||version==SnmpConstants.version3)){
return target;
}
if(version==SnmpConstants.version3){
target = new UserTarget();
//snmpV3需要设置安全级别和安全名称,其中安全名称是创建snmp指定user设置的new OctetString("SNMPV3")
target.setSecurityLevel(SecurityLevel.AUTH_PRIV);
target.setSecurityName(new OctetString(this.username));
}else {
target=new CommunityTarget();
//snmpV1和snmpV2需要指定团体名名称
target.setSecurityName(new OctetString(this.username));
if(version==SnmpConstants.version2c){
target.setSecurityModel(SecurityModel.SECURITY_MODEL_SNMPv2c);
}
}
target.setVersion(version);
target.setAddress(GenericAddress.parse(this.address));
target.setRetries(3);
target.setTimeout(2000);
return target;
}
/**
* 配置设备字符串类型的属性,封装成报文添加到PDU中
* @param pdu
* @param oid
* @param var
*/
public static void setStringVar(PDU pdu,String oid,String var){
OID oidStr = new OID();
oidStr.setValue(oid);
VariableBinding ipBind = new VariableBinding(oidStr,new OctetString(var));
pdu.add(ipBind);
}
/**
* 配置设备数字类型的属性,封装成报文添加到PDU中
* @param pdu
* @param oid
* @param var
*/
public static void setIntVar(PDU pdu,String oid,int var){
OID oidStr = new OID();
oidStr.setValue(oid);
VariableBinding ipBind = new VariableBinding(oidStr,new Integer32(var));
pdu.add(ipBind);
}
/**
* 配置设备数字类型的属性,封装成Guage类型报文添加到PDU中
* @param pdu
* @param oid
* @param var
*/
public static void setGuage(PDU pdu,String oid,long var){
OID oidStr = new OID();
oidStr.setValue(oid);
VariableBinding ipBind = new VariableBinding(oidStr,new Gauge32(var));
pdu.add(ipBind);
}
public static void setIpAddress(PDU pdu,String oid,String var){
OID oidStr = new OID();
oidStr.setValue(oid);
VariableBinding ipBind = new VariableBinding(oidStr,new IpAddress(var));
pdu.add(ipBind);
}
/**
* 创建报文
* @param version
* @param type
* @param oid
* @return
*/
private static PDU createPDU(int version,int type,String oid){
PDU pdu=null;
if(version==SnmpConstants.version3){
pdu= new ScopedPDU();
}else {
pdu= new PDUv1();
}
pdu.setType(type);
//可以添加多个变量oid
/*for(String oid:oids){
pdu.add(new VariableBinding(new OID(oid)));
}*/
pdu.add(new VariableBinding(new OID(oid)));
return pdu;
}
/**
* get方式获取属性
* @param oid
* @return
*/
public List<Map> snmpGet(String oid){
try{
List<Map> list= new ArrayList<Map>();
initSnmp();
Target target = this.createTarget(oid);
PDU pdu=createPDU(1,PDU.GET,oid);
ResponseEvent responseEvent = snmp.send(pdu,target);
PDU response=responseEvent.getResponse();
if(null==response){
System.out.println("Timeout.....");
}else {
if(response.getErrorStatus()==PDU.noError){
Vector<? extends VariableBinding> vbs= response.getVariableBindings();
for (VariableBinding vb: vbs
) {
Map map = new HashMap();
map.put("value",vb.getVariable());
list.add(map);
}
return list;
}else {
System.out.println("Error:"+response.getErrorStatusText());
}
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 设置属性值
* @param oid
* @return
*/
public boolean setProprety(String oid) {
boolean bool = false;
try{
initSnmp();
Target target = this.createTarget(oid);
PDU pdu=createPDU(1,PDU.SET,oid);
ResponseEvent responseEvent = snmp.send(pdu, target);
PDU result = responseEvent.getResponse();
if(result!=null){
System.out.println("result:"+result.toString());
if (result.getErrorStatus() == result.noError) {
bool = true;
}
}
}catch (IOException e){
e.printStackTrace();
}
return bool;
}
public void snmpwalk(String oid){
try{
List<Map> list= new ArrayList<Map>();
initSnmp();
Target target = this.createTarget(oid);
PDU pdu=createPDU(1,PDU.GETNEXT,oid);
boolean matched=true;
while (matched){
ResponseEvent responseEvent = snmp.send(pdu,target);
if(responseEvent==null||responseEvent.getResponse()==null){
break;
}
PDU response=responseEvent.getResponse();
String nextOid=null;
Vector<? extends VariableBinding> vbs= response.getVariableBindings();
for (int i = 0; i <vbs.size() ; i++) {
Map map = new HashMap();
VariableBinding vb= vbs.elementAt(i);
Variable variable= vb.getVariable();
nextOid=vb.getOid().toDottedString();
if(!nextOid.startsWith(oid)){
matched=false;
break;
}
map.put("oid",nextOid);
map.put("value",variable);
list.add(map);
}
if(!matched){
break;
}
pdu.clear();
pdu.add(new VariableBinding(new OID(nextOid)));
}
}catch (IOException e){
}
}
//trap
class TrapReceiver implements CommandResponder{
//用户名
private String username = "admin";
//鉴权密码
private String authPassword = "123";
//数据加密密码
private String privPassword = "123";
//trap地址
private String address = "udp:192.168.0.15/162";
private MultiThreadedMessageDispatcher dispatcher;
private Snmp snmp = null;
private Address listenAddress;
private ThreadPool threadPool;
private void init() throws UnknownHostException, IOException {
try {
//创建接收SnmpTrap的线程池,参数: 线程名称及线程数
threadPool = ThreadPool.create("Trap", 2);
//创建一个多线程消息分发器,以同时处理传入的消息,该实例将用于分派传入和传出的消息
dispatcher = new MultiThreadedMessageDispatcher(threadPool,
new MessageDispatcherImpl());
//监听端的 ip地址 和 监听端口号
listenAddress = GenericAddress.parse(address);
//在指定的地址上创建UDP传输
TransportMapping<?> transport;
if (listenAddress instanceof UdpAddress) {
//必须是本机地址
transport = new DefaultUdpTransportMapping((UdpAddress) listenAddress);
} else {
transport = new DefaultTcpTransportMapping((TcpAddress) listenAddress);
}
//初始化snmp需要设置messageDispatcher里面的参数和TransportMapping参数
snmp = new Snmp(dispatcher, transport);
//消息分发器添加接收的版本信息
/* v1和v2都具有基本的读、写MIB功能。*
* v2增加了警报、批量数据获取、管理站和管理站通信能力。*
* v3在v2的基础上增加了USM,使用加密的数据和用户验证技术,提高了安全性*/
snmp.getMessageDispatcher().addMessageProcessingModel(new MPv3());
snmp.getMessageDispatcher().addMessageProcessingModel(new MPv2c());
snmp.getMessageDispatcher().addMessageProcessingModel(new MPv1());
//创建具有所提供安全协议支持的USM,//根据本地IP地址和其他四个随机字节创建本地引擎ID
USM usm = new USM(SecurityProtocols.getInstance(), new OctetString(MPv3.createLocalEngineID()), 0);
SecurityModels.getInstance().addSecurityModel(usm);
// 添加安全协议,如果没有发过来的消息没有身份认证,可以跳过此段代码
SecurityProtocols.getInstance().addDefaultProtocols();
// 创建和添加用户
OctetString userName1 = new OctetString(username);
OctetString authPass = new OctetString(authPassword);
OctetString privPass = new OctetString(privPassword);
UsmUser usmUser1 = new UsmUser(userName1, AuthMD5.ID, authPass, PrivAES128.ID, privPass);
//因为接受的Trap可能来自不同的主机,主机的Snmp v3加密认证密码都不一样,所以根据加密的名称,来添加认证信息UsmUser。
//添加了加密认证信息的便可以接收来自发送端的信息。
UsmUserEntry userEnty1 = new UsmUserEntry(userName1, usmUser1);
UsmUserTable userTable = snmp.getUSM().getUserTable();
// 添加其他用户
userTable.addUser(userEnty1);
//开启Snmp监听,可以接收来自Trap端的信息。
snmp.listen();
snmp.addCommandResponder(this);
}catch (Exception e){
e.printStackTrace();
}
}
public void run() {
try {
init();
snmp.addCommandResponder(this);
System.out.println("开始监听Trap信息!");
} catch (Exception ex) {
ex.printStackTrace();
}
}
/**
* 实现CommandResponder的processPdu方法, 用于处理传入的请求、PDU等信息
* 当接收到trap时,会自动进入这个方法
*
* @param respEvnt
*/
@Override
public void processPdu(CommandResponderEvent respEvnt) {
// 解析Response
System.out.println("trap接受到告警消息,开始对消息进行处理");
try {
if (respEvnt != null && respEvnt.getPDU() != null) {
PDU pdu=respEvnt.getPDU();
Vector<? extends VariableBinding> vbs= pdu.getVariableBindings();
for (int i = 0; i <vbs.size() ; i++) {
System.out.println("消息体oid:"+vbs.elementAt(i).getOid());
System.out.println("消息体oid对应值:"+vbs.elementAt(i).getVariable());
}
}
}catch (Exception e){
e.printStackTrace();
}
}
}
}
实际SNMP报文类型还有getBulk,getNext等类型,有兴趣的小伙伴可以自己尝试一下。还有一款MIB Browser(MIB浏览器)是SNMP开发中必备一种工具,有时间再讲一下它的安装和使用。