SNMP协议实践
SNMP协议实践
一、背景
物联网时代,软件系统的建设越来越多的需要与设备、设备采集通信协议进行打交道。
工作中,遇到对华为桌面云实现统一监控运维的项目需求。基本功能要求是,通过对华为桌面云FA、FC平台北向接口的数据采集,实现对华为桌面云服务器运行状态的监测,包括CPU使用率、内存、网络、硬盘等使用情况,故障告警的监听处理等内容。技术预研阶段涉及对SNMP协议的预研了解,以下是对SNMP协议内容的一些总结,涉及内容主要如下:
- Snmp基本概念
 - linux下安装和配置SNMP
 - JAVA开源组件snmp4j
 
二、Snmp基本概念
2.1 什么是SNMP协议
 SNMP是英文"Simple Network Management Protocol"的缩写,简单网络管理协议(SNMP) 是专门设计用于在 IP 网络管理网络节点(服务器、工作站、路由器、交换机、HUBS等)的一种标准协议,它属于TCP/IP五层协议中的应用层协议,用于网络管理的协议。SNMP主要用于网络设备的管理。由于SNMP协议简单可靠 ,受到了众多厂商的欢迎,成为了目前最为广泛的网管协议。
			SNMP协议由SNMP管理站(manager)和SNMP代理(Agent)两部分构成。SNMP管理站是中心节点,负责收集维护各个SNMP元素的信息,并对这些信息进行处理;SNMP代理运行在各个被管理的网络节点上,负责统计该节点的各项指标信息,并且负责与SNMP管理站交互,接收并执行管理站的命令,上传各种本地的网络信息。

2.2 SNMP组成
一套完整的SNMP系统主要包括管理信息库(MIB)、管理信息结构(SMI)及SNMP报文协议。
- MIB:任何一个被管理的资源都表示成一个对象,称为被管理的对象。MIB是被管理对象的集合。它定义了被管理对象的一系列属性:对象的名称、对象的访问权限和对象的数据类型等。每个SNMP设备(Agent)都有自己的MIB。MIB也可以看作是NMS和Agent之间的沟通桥梁。
 

- SMI:SMI定义了SNMP框架所用信息的组织、组成和标识,它还为描述MIB对象和描述协议怎样交换信息奠定了基础。
 - SNMP报文:SNMP协议定义了数据包的格式,一条SNMP消息由"版本号"、"SNMP共同体名"和"协议数据单元(PDU)"构成,数据包的长度不是固定的。
 

2.3 SNMP协议工作方式
 SNMP主要采用UDP协议在manager和agent之间传输信息。 SNMP采用UDP 161端口接收和发送请求,162端口接收trap,执行SNMP的设备缺省都必须采用这些端口。SNMP消息全部通过UDP端口161接收,只有Trap信息采用UDP端口162。
 简单讲,SNMP管理站通过UDP协议向SNMP代理发送各种命令,当SNMP代理收到命令后,返回SNMP管理站需要的参数。当SNMP代理检测到网络元素异常的时候,也可以主动向SNMP管理站发送消息,通告当前异常状况。SNMP协议提供了三种用于控制MIB对象的基本操作命令:
- Get:用于读取代理者处对象的值。它是SNMP协议中使用率最高的一个命令,该命令是从网络设备中获得管理信息的基本方式。
 - Set:manager需要向设备执行设置操作,SNMP提供了Set操作,可以通过它来改动设备的配置或控制设备的运转状态。它可以设置设备的名称、关掉一个端口或清除一个地址解析表中的项等。
 - Trap: agent主动向manager通报重要事件。它的主要用途是在网络管理系统没有明确要求的前提下,由管理代理通知网络管理系统有一些特别的情况或问题 发生了。如果发生意外情况,客户会向服务器的162端口发送一个消息,告知服务器指定的变量值发生了变化。通常由服务器请求而获得的数据由服务器的161 端口接收。Trap 消息可以用来通知管理站线路的故障、连接的终端和恢复、认证失败等消息。管理站可相应的作出处理。
 
2.4 SNMP 协议版本
 SNMP协议在1988年被制定,1992年发布了SNMPv2版本,以增强SNMPv1的安全性和功能。目前SNMP协议共有v1,v2,v3三个版本:
- SNMP v1是SNMP协议的最初版本,不过依然是众多厂家实现SNMP基本方式。
 - SNMP v2通常被指是基于community的SNMP V2。Community实质上就是**。
 - SNMP v3 是最新版本的SNMP。它对网络管理最大的贡献在于其安全性。增加了对认证和密文传输的支持。
 
三、linux下安装和配置SNMP
3.1安装snmp
# 安装SNMP
yum install -y net-snmp*
# 备份SNMP配置
/etc/snmp/snmpd.conf /etc/snmp/snmpd.conf.bak
# 添加修改配置
vim /etc/snmp/snmpd.conf
rocommunity SZ-SNMP-PRIVATE
group   notConfigGroup v1           notConfigUser
group   notConfigGroup v2c           notConfigUser
# 重启SNMP
service snmpd restart
# 查看主机
snmpwalk -v 2c -c SZ-SNMP-PRIVATE 192.168.1.112
3.2 常用OID
| 系统参数(1.3.6.1.2.1.1) | |||
|---|---|---|---|
| OID | 描述 | 备注 | 请求方式 | 
| .1.3.6.1.2.1.1.1.0 | 获取系统基本信息 | SysDesc | GET | 
| .1.3.6.1.2.1.1.3.0 | 监控时间 | sysUptime | GET | 
| .1.3.6.1.2.1.1.4.0 | 系统联系人 | sysContact | GET | 
| .1.3.6.1.2.1.1.5.0 | 获取机器名 | SysName | GET | 
| .1.3.6.1.2.1.1.6.0 | 机器坐在位置 | SysLocation | GET | 
| .1.3.6.1.2.1.1.7.0 | 机器提供的服务 | SysService | GET | 
| .1.3.6.1.2.1.25.4.2.1.2 | 系统运行的进程列表 | hrSWRunName | WALK | 
| .1.3.6.1.2.1.25.6.3.1.2 | 系统安装的软件列表 | hrSWInstalledName | WALK | 
| 网络接口(1.3.6.1.2.1.2) | |||
|---|---|---|---|
| OID | 描述 | 备注 | 请求方式 | 
| .1.3.6.1.2.1.2.1.0 | 网络接口的数目 | IfNumber | GET | 
| .1.3.6.1.2.1.2.2.1.2 | 网络接口信息描述 | IfDescr | WALK | 
| .1.3.6.1.2.1.2.2.1.3 | 网络接口类型 | IfType | WALK | 
| .1.3.6.1.2.1.2.2.1.4 | 接口发送和接收的最大IP数据报[BYTE] | IfMTU | WALK | 
| .1.3.6.1.2.1.2.2.1.5 | 接口当前带宽[bps] | IfSpeed | WALK | 
| .1.3.6.1.2.1.2.2.1.6 | 接口的物理地址 | IfPhysAddress | WALK | 
| .1.3.6.1.2.1.2.2.1.8 | 接口当前操作状态[up|down] | IfOperStatus | WALK | 
| .1.3.6.1.2.1.2.2.1.10 | 接口收到的字节数 | IfInOctet | WALK | 
| .1.3.6.1.2.1.2.2.1.16 | 接口发送的字节数 | IfOutOctet | WALK | 
| .1.3.6.1.2.1.2.2.1.11 | 接口收到的数据包个数 | IfInUcastPkts | WALK | 
| .1.3.6.1.2.1.2.2.1.17 | 接口发送的数据包个数 | IfOutUcastPkts | WALK | 
| CPU及负载 | |||
|---|---|---|---|
| OID | 描述 | 备注 | 请求方式 | 
| . 1.3.6.1.4.1.2021.11.9.0 | 用户CPU百分比 | ssCpuUser | GET | 
| . 1.3.6.1.4.1.2021.11.10.0 | 系统CPU百分比 | ssCpuSystem | GET | 
| . 1.3.6.1.4.1.2021.11.11.0 | 空闲CPU百分比 | ssCpuIdle | GET | 
| . 1.3.6.1.4.1.2021.11.50.0 | 原始用户CPU使用时间 | ssCpuRawUser | GET | 
| .1.3.6.1.4.1.2021.11.51.0 | 原始nice占用时间 | ssCpuRawNice | GET | 
| . 1.3.6.1.4.1.2021.11.52.0 | 原始系统CPU使用时间 | ssCpuRawSystem. | GET | 
| . 1.3.6.1.4.1.2021.11.53.0 | 原始CPU空闲时间 | ssCpuRawIdle | GET | 
| . 1.3.6.1.2.1.25.3.3.1.2 | CPU的当前负载,N个核就有N个负载 | hrProcessorLoad | WALK | 
| . 1.3.6.1.4.1.2021.11.3.0 | ssSwapIn | GET | |
| . 1.3.6.1.4.1.2021.11.4.0 | SsSwapOut | GET | |
| . 1.3.6.1.4.1.2021.11.5.0 | ssIOSent | GET | |
| . 1.3.6.1.4.1.2021.11.6.0 | ssIOReceive | GET | |
| . 1.3.6.1.4.1.2021.11.7.0 | ssSysInterrupts | GET | |
| . 1.3.6.1.4.1.2021.11.8.0 | ssSysContext | GET | |
| . 1.3.6.1.4.1.2021.11.54.0 | ssCpuRawWait | GET | |
| . 1.3.6.1.4.1.2021.11.56.0 | ssCpuRawInterrupt | GET | |
| . 1.3.6.1.4.1.2021.11.57.0 | ssIORawSent | GET | |
| . 1.3.6.1.4.1.2021.11.58.0 | ssIORawReceived | GET | |
| . 1.3.6.1.4.1.2021.11.59.0 | ssRawInterrupts | GET | |
| . 1.3.6.1.4.1.2021.11.60.0 | ssRawContexts | GET | |
| . 1.3.6.1.4.1.2021.11.61.0 | ssCpuRawSoftIRQ | GET | |
| . 1.3.6.1.4.1.2021.11.62.0 | ssRawSwapIn. | GET | |
| . 1.3.6.1.4.1.2021.11.63.0 | ssRawSwapOut | GET | |
| .1.3.6.1.4.1.2021.10.1.3.1 | Load5 | GET | |
| .1.3.6.1.4.1.2021.10.1.3.2 | Load10 | GET | |
| .1.3.6.1.4.1.2021.10.1.3.3 | Load15 | GET | 
| 内存及磁盘(1.3.6.1.2.1.25) | |||
|---|---|---|---|
| OID | 描述 | 备注 | 请求方式 | 
| .1.3.6.1.2.1.25.2.2.0 | 获取内存大小 | hrMemorySize | GET | 
| .1.3.6.1.2.1.25.2.3.1.1 | 存储设备编号 | hrStorageIndex | WALK | 
| .1.3.6.1.2.1.25.2.3.1.2 | 存储设备类型 | hrStorageType[OID] | WALK | 
| .1.3.6.1.2.1.25.2.3.1.3 | 存储设备描述 | hrStorageDescr | WALK | 
| .1.3.6.1.2.1.25.2.3.1.4 | 簇的大小 | hrStorageAllocationUnits | WALK | 
| .1.3.6.1.2.1.25.2.3.1.5 | 簇的的数目 | hrStorageSize | WALK | 
| .1.3.6.1.2.1.25.2.3.1.6 | 使用多少,跟总容量相除就是占用率 | hrStorageUsed | WALK | 
| .1.3.6.1.4.1.2021.4.3.0 | Total Swap Size(虚拟内存) | memTotalSwap | GET | 
| .1.3.6.1.4.1.2021.4.4.0 | Available Swap Space | memAvailSwap | GET | 
| .1.3.6.1.4.1.2021.4.5.0 | Total RAM in machine | memTotalReal | GET | 
| .1.3.6.1.4.1.2021.4.6.0 | Total RAM used | memAvailReal | GET | 
| .1.3.6.1.4.1.2021.4.11.0 | Total RAM Free | memTotalFree | GET | 
| .1.3.6.1.4.1.2021.4.13.0 | Total RAM Shared | memShared | GET | 
| .1.3.6.1.4.1.2021.4.14.0 | Total RAM Buffered | memBuffer | GET | 
| .1.3.6.1.4.1.2021.4.15.0 | Total Cached Memory | memCached | GET | 
| .1.3.6.1.4.1.2021.9.1.2 | Path where the disk is mounted | dskPath | WALK | 
| .1.3.6.1.4.1.2021.9.1.3 | Path of the device for the partition | dskDevice | WALK | 
| .1.3.6.1.4.1.2021.9.1.6 | Total size of the disk/partion (kBytes) | dskTotal | WALK | 
| .1.3.6.1.4.1.2021.9.1.7 | Available space on the disk | dskAvail | WALK | 
| .1.3.6.1.4.1.2021.9.1.8 | Used space on the disk | dskUsed | WALK | 
| .1.3.6.1.4.1.2021.9.1.9 | Percentage of space used on disk | dskPercent | WALK | 
| .1.3.6.1.4.1.2021.9.1.10 | Percentage of inodes used on disk | dskPercentNode | WALK | 
四、JAVA开源组件snmp4j
4.1 snmp4j简介
SNMP4J是一个用Java来实现SNMP协议的开源项目.它支持以命令行的形式进行管理与响应。SNMP4J是纯面向对象设计与SNMP++(用C++实现SNMPv1/v2c/v3)相类似。
SNMP4J API 提供以下下特性:
- 支持MD5和SHA验证,DES,3DES,AES128、AES192和AES256加密的SNMPv3。
 - 支持MPv1,MPv2C和MPv3,带执行的可阻塞的信息处理模块。
 - 全部PDU格式。
 - 可阻塞的传输拓扑。支持UPD、TCP、TLS 。
 - 可阻塞的超时模块。
 - 同步和异步请求。
 - 命令发生器以及命令应答器的支持。
 - 基于Apache license的开源免费。
 - JAVA 1.4.1或更高版本(2.0或更高版本需要jdk1.6及以上的支持)。
 - 基于LOG4J记录日志。
 - 使用GETBULK实现Row-based的有效的异步表格获取。
 - 支持多线程。
 
4.2 snmp4j示例
jar包引入
<dependency>
    <groupId>org.snmp4j</groupId>
    <artifactId>snmp4j</artifactId>
    <version>2.7.0</version>
</dependency>
示例代码
package com.wf.example.snmp;
import org.snmp4j.*;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.smi.*;
import org.snmp4j.transport.DefaultUdpTransportMapping;
import java.io.IOException;
import java.util.Vector;
/**
 * @author wf
 * @ClassName:SnmpUtil
 * @Description: SNMP协议
 * @date 2021/11/16 14:36
 */
public class SnmpDemo {
    private Snmp snmp = null;
    private Address targetAddress = null;
    //SNMP 代理地址
    private String agentAddress="";
    //SNMP trap上报监听地址
    private String trapAddress="";
    //[身份认证]组织团体名称
    private String agentCommunity="";
    /**
     * ========================
     * 测试SNMP协议
     * 1、get方法获取信息(只演示了同步方式)
     * 2、set方法修改信息
     * 3、trap监听告警上报
     * ========================
     */
    public static void main(String[] args) {
        try {
            //本机(windows 10)测试:GET获取信息【成功】、SET修改信息【成功】
            System.out.println("======本机测试开始======");
            getPDUTest("udp:127.0.0.1/161","public");
            setPDUTest("udp:127.0.0.1/161","public");
            //配合模拟发送程序进行接收:SnmpUtilSendTrap
            SnmpDemo util = new SnmpDemo("udp:127.0.0.1/161","192.168.215.160/162","public");
            util.listen();
            System.out.println("======本机测试结束======");
            //121.37.0.11(linux)服务器测试:GET获取信息【成功】、SET修改信息【成功】 
            System.out.println("======121.37.0.11服务器测试开始======");
            getPDUTest("udp:121.37.0.11/161","SZ-SNMP-PRIVATE");
            //setPDUTest("udp:121.37.0.11/161","SZ-SNMP-PRIVATE");
            System.out.println("======121.37.0.11服务器测试结束======");
            //192.168.21.110 华为云桌面测试服务器:trap报警【成功】、GET获取信息【成功】
            System.out.println("======192.168.22.110服务器测试开始======");
            //getPDUTest("udp:192.168.22.110/161","talkweb");
            SnmpDemo util1 = new SnmpDemo("udp:192.168.22.110/161","192.168.215.160/162","talkweb");
            util1.listen();
            System.out.println("======192.168.22.110服务器测试结束======");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * GET测试,需要预装配置SNMP协议,并进行团体名称,读写权限配置
     * 1、测试通过:读取计算机名称
     */
    public static void getPDUTest(String agentAddress,String agentCommunity)throws IOException{
        //本机测试==测试通过
        SnmpDemo util = new SnmpDemo(agentAddress,agentCommunity);
        // 获取机器名 .1.3.6.1.2.1.1.5.0
        // 获取系统基本信息 .1.3.6.1.2.1.1.1.0
        OID[] oids=new OID[]{new OID(".1.3.6.1.2.1.1.5.0"),new OID(".1.3.6.1.2.1.1.1.0")};
        //获取计算机名称
        util.getPDU(oids);
        //设置计算机名称  DESKTOP-E109030 |
    }
    /**
     * SET测试,需要预装配置SNMP协议,并进行团体名称,读写权限配置
     * 新建团体名称:默认权限是只读,写入权限需要另外分配设置
     * 1、测试通过:改写计算机名称(只能改写到MIB库中,不能反馈到计算机的信息面板)
     */
    public static void setPDUTest(String agentAddress,String agentCommunity)throws IOException{
        //本机测试==测试通过
        SnmpDemo util = new SnmpDemo(agentAddress,agentCommunity);
        // 机器名 .1.3.6.1.2.1.1.5.0
        VariableBinding[] variableBindings=new VariableBinding []{new VariableBinding(new OID(".1.3.6.1.2.1.1.5.0"),new OctetString("ttt"))};
        //获取计算机名称
        util.setPDU(variableBindings);
    }
    public SnmpDemo(String agentAddress, String agentCommunity) throws IOException{
        this.agentAddress=agentAddress;
        this.agentCommunity=agentCommunity;
        this.initComm();
    }
    /**
     * 初始化
     */
    public void initComm() throws IOException {
        // 设置Agent方的IP和端口
        targetAddress = GenericAddress.parse(agentAddress);
        TransportMapping transport = new DefaultUdpTransportMapping();
        snmp = new Snmp(transport);
        transport.listen();
    }
    /**
     * 通过OID获得一个或多个信息
     * @param oids
     */
    public void getPDU(OID [] oids) throws IOException {
        PDU pdu = new PDU();
        for (OID oid:oids){
            pdu.add(new VariableBinding(oid));
        }
        pdu.setType(PDU.GET);
        readResponse(sendPDU(pdu));
    }
    /**
     * 设置变更
     * @param variableBindings 变更绑定
     */
    public void setPDU(VariableBinding [] variableBindings) throws IOException {
        PDU pdu = new PDU();
        for(VariableBinding variableBinding:variableBindings){
            pdu.add(variableBinding);
        }
        pdu.setType(PDU.SET);
        //发送
        readResponse(sendPDU(pdu));
    }
    /**
     * 发送信息
     */
    public ResponseEvent sendPDU(PDU pdu) throws IOException {
        // 设置target
        CommunityTarget target = new CommunityTarget();
        //设置 团体名称
        target.setCommunity(new OctetString(agentCommunity));
        //设置地址
        target.setAddress(targetAddress);
        // 通信不成功时的重试次数
        target.setRetries(2);
        // 超时时间
        target.setTimeout(1500);
        //指定协议
        target.setVersion(SnmpConstants.version2c);
        // 向Agent发送PDU,并返回Response
        ResponseEvent event = snmp.send(pdu, target);
        //关闭
        snmp.close();
        return event;
    }
    /**
     * 读取信息
     * @param respEvnt
     */
    public void readResponse(ResponseEvent respEvnt) {
        // 解析Response
        if (respEvnt != null && respEvnt.getResponse() != null) {
           // System.out.println(respEvnt.getPeerAddress());
            System.out.println("【请求】"+respEvnt.getRequest());
            System.out.println("【返回】"+respEvnt.getResponse());
            Vector<VariableBinding> recVBs = (Vector<VariableBinding>) respEvnt.getResponse().getVariableBindings();
            for (int i = 0; i < recVBs.size(); i++) {
                VariableBinding recVB = recVBs.elementAt(i);
                System.out.println(recVB.getOid() + " : " + recVB.getVariable());
            }
        }
    }
    ////////////////////////////////////////////////////////////
    private TransportMapping transport = null;
    public SnmpDemo(String agentAddress, String trapAddress, String agentCommunity)throws IOException {
        this.agentAddress=agentAddress;
        this.agentCommunity=agentCommunity;
        //监听地址
        this.trapAddress=trapAddress;
        this.initComm1();
    }
    public void initComm1() throws IOException {
        // 设置Agent方的IP和端口
        targetAddress = GenericAddress.parse(agentAddress);
        // 设置接收trap的IP和端口
        transport = new DefaultUdpTransportMapping(new UdpAddress(trapAddress));
        snmp = new Snmp(transport);
        CommandResponder trapRec = new CommandResponder() {
            @Override
            public synchronized void processPdu(CommandResponderEvent e) {
                // 接收trap
                PDU command = e.getPDU();
                if (command != null) {
                    System.out.println(command.toString());
                }
                VariableBinding value = null;
                /*Iterator<VariableBinding> size = command.getVariableBindings().iterator();
                while(size.hasNext()){
                    value = size.next();
                    System.out.println(value.getOid().toString()+" : "+value.getVariable().toString());
                }*/
            }
        };
        snmp.addCommandResponder(trapRec);
        transport.listen();
    }
    public synchronized void listen() {
        System.out.println("Waiting for traps..");
        try {
            this.wait();//Wait for traps to come in
        } catch (InterruptedException ex) {
            System.out.println("Interrupted while waiting for traps: " + ex);
            System.exit(-1);
        }
    }
}





















学习了。