<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>绿色记忆 &#187; SNMP</title>
	<atom:link href="https://blog.gmem.cc/tag/snmp/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.gmem.cc</link>
	<description></description>
	<lastBuildDate>Mon, 06 Apr 2026 12:46:48 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.9.14</generator>
	<item>
		<title>Ubuntu下安装SNMP组件</title>
		<link>https://blog.gmem.cc/snmp-under-ubuntu</link>
		<comments>https://blog.gmem.cc/snmp-under-ubuntu#comments</comments>
		<pubDate>Thu, 10 Sep 2015 06:59:56 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[SNMP]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=8370</guid>
		<description><![CDATA[<p>网络管理站（Manager） 安装必要的软件： [crayon-69d78d8fa42ea713133687/] 修改配置文件：[crayon-69d78d8fa42f2240184467-i/]  注释掉：[crayon-69d78d8fa42f4565476058-i/] ，以允许Manager可以导入额外的MIBs。可以在此文件中设置默认的身份验证信息： [crayon-69d78d8fa42f6313974734/] 这样，该Manager可以使用简短的命令来访问Agent： [crayon-69d78d8fa42f8886230940/] 代理服务器（Agent） [crayon-69d78d8fa42fa390626361/] 修改配置文件：[crayon-69d78d8fa42fc162208840-i/] ，如下： [crayon-69d78d8fa42fe531770959/] 重新启动代理服务器： [crayon-69d78d8fa4301972107082/] 在Manger上尝试执行一个get-request，确认代理服务器正常运作： [crayon-69d78d8fa4303788825842/] 以bootstrap为模板，创建用户snmp： [crayon-69d78d8fa4305182661476/] 修改密码为snmppswd： [crayon-69d78d8fa4307850708184/] 现在可以尝试使用新用户了： [crayon-69d78d8fa4309537493777/] <a class="read-more" href="https://blog.gmem.cc/snmp-under-ubuntu">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/snmp-under-ubuntu">Ubuntu下安装SNMP组件</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h3"><span class="graybg">网络管理站（Manager）</span></div>
<p>安装必要的软件：</p>
<pre class="crayon-plain-tag">sudo apt-get update
#Manager组件
sudo apt-get install snmp
#包含标准化MIBs信息的组件，允许通过名字而不是OID访问绝大部分MIB树分支
sudo apt-get install snmp-mibs-downloader</pre>
<p>修改配置文件：<pre class="crayon-plain-tag">sudo vim /etc/snmp/snmp.conf</pre>  注释掉：<pre class="crayon-plain-tag">#mibs :</pre> ，以允许Manager可以导入额外的MIBs。可以在此文件中设置默认的身份验证信息：</p>
<pre class="crayon-plain-tag">defSecurityName snmp
defSecurityLevel authPriv
defAuthType MD5
defPrivType DES
defAuthPassphrase snmppswd
defPrivPassphrase snmppswd</pre>
<p>这样，该Manager可以使用简短的命令来访问Agent：</p>
<pre class="crayon-plain-tag">snmpget  192.168.0.90 sysUpTime.0</pre>
<div class="blog_h3"><span class="graybg">代理服务器（Agent）</span></div>
<pre class="crayon-plain-tag">#SNMP代理守护程序，能够响应SNMP请求
sudo apt-get install snmpd</pre>
<p>修改配置文件：<pre class="crayon-plain-tag">sudo vim /etc/snmp/snmpd.conf</pre> ，如下：</p>
<pre class="crayon-plain-tag">#解除下面这一行的注释，使之监听所有地址，默认监听本地地址
#上面监听本地地址的行，注释掉
agentAddress udp:161,udp6:[::1]:161

### SNMPv1、SNMPv2c ###
#基于团体名的验证
rocommunity public              #只读团体名
rwcommunity authenticated       #读写团体名
rocommunity6 public6            #IPv6
rwcommunity6 authenticated6


### SNMPv3  ###
#为了应答SNMPv3请求，代理需要提供唯一性的engineID
engineID 192.168.0.90
#基于用户的验证
#添加下面一行，创建一个临时的bootstrap用户
#使用MD5验证，密码为短语为password
#使用DES加解密，密码默认为验证密码
createUser bootstrap MD5 password DES
#设置用户的权限为读写（rwuser，rouser表示只读），并且强制加密（priv）
rwuser bootstrap priv
#这个用户马上创建
rwuser snmp priv</pre>
<p>重新启动代理服务器：</p>
<pre class="crayon-plain-tag">sudo service snmpd restart</pre>
<p>在Manger上尝试执行一个get-request，确认代理服务器正常运作：</p>
<pre class="crayon-plain-tag">snmpget -u bootstrap -l authPriv -a MD5 -x DES -A password -X password 192.168.0.90 1.3.6.1.2.1.1.1.0
#输出：SNMPv2-MIB::sysDescr.0 = STRING: Linux amethystine 3.13.0-32-generic #57-Ubuntu SMP...</pre>
<p>以bootstrap为模板，创建用户snmp：</p>
<pre class="crayon-plain-tag">snmpusm -u bootstrap -l authPriv -a MD5 -x DES -A password -X password 192.168.0.90 create snmp bootstrap
#User successfully created.</pre>
<p>修改密码为snmppswd：</p>
<pre class="crayon-plain-tag">snmpusm -u snmp -l authPriv -a MD5 -x DES -A password -X password 192.168.0.90 passwd password snmppswd
#SNMPv3 Key(s) successfully changed.</pre>
<p>现在可以尝试使用新用户了：</p>
<pre class="crayon-plain-tag">snmpget -u snmp -l authPriv -a MD5 -x DES -A snmppswd -X snmppswd 192.168.0.90 sysUpTime.0</pre>
<p>回到代理服务器，注释掉bootstrap用户的创建语句：</p>
<pre class="crayon-plain-tag">#createUser bootstrap MD5 password DES</pre>
<p> 再次切换到Manager，使用命令把用户从usmUserTable删除：</p>
<pre class="crayon-plain-tag">snmpusm -u snmp -l authPriv -a MD5 -x DES -A snmppswd -X snmppswd 192.168.0.90  delete bootstrap</pre>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/snmp-under-ubuntu">Ubuntu下安装SNMP组件</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/snmp-under-ubuntu/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>snmp4j学习笔记</title>
		<link>https://blog.gmem.cc/snmp4j-study-note</link>
		<comments>https://blog.gmem.cc/snmp4j-study-note#comments</comments>
		<pubDate>Wed, 09 Sep 2015 06:11:03 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[Network]]></category>
		<category><![CDATA[SNMP]]></category>
		<category><![CDATA[学习笔记]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=8254</guid>
		<description><![CDATA[<p>snmp4j简介 snmp4j是Java社区首选的SNMP协议的开源实现，支持JDK1.4+版本，该框架受C++库snmp++启发，以面向对象的方式进行设计。snmp4j同时支持Manager、Agent两种角色，支持同步、异步通信方式，以及多种验证方式和加密算法。snmp4j的具体特性如下： 支持MD5、SHA身份验证 支持SNMPv3的DES、3DES、AES128、AES192、AES256加密 支持协议：SNMPv1、SNMPv2c、SNMPv3 支持全部PDU类型 支持多种传输层协议：UDP、TCP、TLS 支持同步和异步请求 支持基于行的（row-based）异步表格获取 核心类型简介 关系示意图 下图描述了snmp4j最重要的核心类之间的关系，其中蓝色箭头代表向远程snmp实体（Target）发送的请求，红色代表远程snmp实体发送给当前snmp会话的请求。 Snmp类，一般称为snmp4j会话，是整个框架的核心，用户代码主要和它进行交互。 snmp4j向外部SNMP实体发送请求的过程如下： 用户代码（Invoker）通过Snmp.send()方法发起请求 TransportMapping组件通过底层的TCP或者UDP协议将请求传输出去 请求、应答的载体均是PDU，对应了SNMP报文 Target代表远程SNMP实体，持有它的地址、身份、SNMP版本等信息 对于同步请求，应答直接返回给Invoker；对于异步请求，应答通过ResponseListener回调，ResponseListener在Invoker发送异步请求时，作为方法参数注册 snmp4j接收外部SNMP实体的请求，并进行处理的过程如下： snmp4j监听（通过TransportMapping）外部SNMP报文，并转为PDU类型 MessageDispatcher负责将PDU转发给（回调）合适的CommandResponder，自定义CommandResponder可以注册到Snmp会话 <a class="read-more" href="https://blog.gmem.cc/snmp4j-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/snmp4j-study-note">snmp4j学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">snmp4j简介</span></div>
<p>snmp4j是Java社区首选的SNMP协议的开源实现，支持JDK1.4+版本，该框架受C++库snmp++启发，以面向对象的方式进行设计。snmp4j同时支持Manager、Agent两种角色，支持同步、异步通信方式，以及多种验证方式和加密算法。snmp4j的具体特性如下：</p>
<ol>
<li>支持MD5、SHA身份验证</li>
<li>支持SNMPv3的DES、3DES、AES128、AES192、AES256加密</li>
<li>支持协议：SNMPv1、SNMPv2c、SNMPv3</li>
<li>支持全部PDU类型</li>
<li>支持多种传输层协议：UDP、TCP、TLS</li>
<li>支持同步和异步请求</li>
<li>支持基于行的（row-based）异步表格获取</li>
</ol>
<div class="blog_h2"><span class="graybg">核心类型简介</span></div>
<div class="blog_h3"><span class="graybg">关系示意图</span></div>
<p>下图描述了snmp4j最重要的核心类之间的关系，其中蓝色箭头代表向远程snmp实体（Target）发送的请求，红色代表远程snmp实体发送给当前snmp会话的请求。</p>
<p><img class="aligncenter size-full wp-image-8436" src="https://blog.gmem.cc/wp-content/uploads/2015/09/snmp4j-api-flow.png" alt="snmp4j-api-flow" width="70%" /></p>
<p>Snmp类，一般称为snmp4j会话，是整个框架的核心，用户代码主要和它进行交互。</p>
<p>snmp4j向外部SNMP实体发送请求的过程如下：</p>
<ol>
<li>用户代码（Invoker）通过Snmp.send()方法发起请求</li>
<li>TransportMapping组件通过底层的TCP或者UDP协议将请求传输出去</li>
<li>请求、应答的载体均是PDU，对应了SNMP报文</li>
<li>Target代表远程SNMP实体，持有它的地址、身份、SNMP版本等信息</li>
<li>对于同步请求，应答直接返回给Invoker；对于异步请求，应答通过ResponseListener回调，ResponseListener在Invoker发送异步请求时，作为方法参数注册</li>
</ol>
<p>snmp4j接收外部SNMP实体的请求，并进行处理的过程如下：</p>
<ol>
<li>snmp4j监听（通过TransportMapping）外部SNMP报文，并转为PDU类型</li>
<li>MessageDispatcher负责将PDU转发给（回调）合适的CommandResponder，自定义CommandResponder可以注册到Snmp会话</li>
<li>CommandResponder负责处理请求PDU，可选的，给出一个应答PDU</li>
</ol>
<div class="blog_h3"><span class="graybg">API说明</span></div>
<p>下表列出snmp4核心类的职责、提供的方法以及代码样例：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 100px; text-align: center;"> 类型</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>Snmp</td>
<td>
<p>SNMP会话类，该类是snmp4j的核心，提供发送/接收SNMP的PDU的功能。支持发送所有类型的PDU，支持异步、同步两种方式发送。</p>
<p>Snmp独立于具体的传输层协议，和传输层有关的工作由TransportMapping负责，有两种方式可以将Snmp关联到某种传输：</p>
<pre class="crayon-plain-tag">//一般我们都是用UDP作为传输层协议
TransportMapping transport = new DefaultUdpTransportMapping();
//可以
Snmp snmp = new Snmp( transport );
//或者
Snmp snmp = new Snmp( );
snmp.addTransportMapping( transport );</pre>
<p>除了关联传输，还可以为Snmp指定消息分发器：</p>
<pre class="crayon-plain-tag">snmp = new Snmp( new MessageDispatcherImpl(), transport );</pre>
<p>Snmp准备好后，需要启动监听，以便接收其它SNMP实体发送来的报文：</p>
<pre class="crayon-plain-tag">transport.listen();</pre>
<p>要发送SNMP报文，首先要构建目标（Taget），目标映射接收方SNMP实体：</p>
<pre class="crayon-plain-tag">Address targetAddress = GenericAddress.parse( "udp:127.0.0.1/161" );

//基于团体名的目标（SNMPv1、SNMPv2c）
CommunityTarget target = new CommunityTarget();
target.setCommunity( new OctetString( "public" ) );
target.setAddress( targetAddress );
target.setVersion( SnmpConstants.version1 );

//基于用户的目标（SNMPv3）
UserTarget target = new UserTarget();
target.setAddress( targetAddress );
target.setVersion( SnmpConstants.version3 );
target.setSecurityLevel( SecurityLevel.AUTH_PRIV );
target.setSecurityName( new OctetString( "ALEX" ) );</pre>
<p>然后，构建PDU，即需要发送的报文：</p>
<pre class="crayon-plain-tag">//SNMPv3的PDU
PDU pdu = new ScopedPDU();
//SNMPv2c的PDU
PDU pdu = new PDU();

//添加变量绑定到PDU
pdu.add( new VariableBinding( new OID( "1.3.6" ) ) );
//设置PDU类型
pdu.setType( PDU.GETNEXT );</pre>
<p>构建好PDU后，就可以通过同步（同步方式不支持广播消息）或者异步方式，将其发送给target了：</p>
<pre class="crayon-plain-tag">//发送PDU，同步得到应答
ResponseEvent response = snmp.send( pdu, target );
//异步发送PDU，应答到达时通过listener 回调
snmp.send( pdu, target, null, listener );</pre>
<p>对于Manager角色，如果要接收来自Agent的trap，可以：</p>
<pre class="crayon-plain-tag">CommandResponder trapPrinter = new CommandResponder() {
    public synchronized void processPdu( CommandResponderEvent e )
    {
        PDU command = e.getPDU();
        //处理trap
        e.setProcessed( true );
    }
};
snmp.addCommandResponder( trapPrinter );</pre>
<p>对于Agent角色，如果要接收来自Manger的请求，并做出应答，还是使用上面的CommandResponder：</p>
<pre class="crayon-plain-tag">public synchronized void processPdu( CommandResponderEvent e )
{
    PDU command = e.getPDU();
    if ( PDU.GET == command.getType() )
    {
        //请求变量的OID
        OID oid = command.get( 0 ).getOid();
        //重用命令对象
        command.setErrorIndex( 0 );
        command.setErrorStatus( 0 );
        command.setType( PDU.RESPONSE );
        int val = query( oid ); //业务方法负责查询OID的值
        command.get( 0 ).setVariable( new Integer32( val ) );
        try
        {
            MessageDispatcher dispatcher = e.getMessageDispatcher();
            //发送应答PDU
            dispatcher.returnResponsePdu(
                    e.getMessageProcessingModel(),
                    e.getSecurityModel(),
                    e.getSecurityName(),
                    e.getSecurityLevel(),
                    command,
                    e.getMaxSizeResponsePDU(),
                    e.getStateReference(),
                    new StatusInformation()
                    );
        }
        catch ( MessageException ex )
        {
            System.out.println( "Failed to send response PDU" );
        }
        finally
        {
            e.setProcessed( true );
        }
    }
}</pre>
<p>最后，应当关闭会话和transport：</p>
<pre class="crayon-plain-tag">snmp.close();
transport.close();</pre>
</td>
</tr>
<tr>
<td>PDU</td>
<td>
<p>在TCP/IP协议族中，协议数据单元（Protocol Data Unit，PDU）表示使用某个协议的两个实体之间发送的报文，包括报文头和报文体。由于TCP/IP协议族是分层结构，因而下层协议PDU的载荷部分，就是上层协议的PDU。
<p>在SNMP协议的报文头中，具有一个字段“PDU类型”，可以理解为报文的类型。</p>
<p>snmp4j使用该类构建SNMP协议报文。该类实现了BERSerializable接口，后者定义了如何将ASN.1数据类型串行化为BER格式的方法。</p>
<p>PDU类定义了以下常量，来代表SNMP报文类型：</p>
<ol>
<li>GET：get-request报文</li>
<li>GETNEXT：get-next-response报文</li>
<li>RESPONSE：get-response、set-response报文</li>
<li>SET：set-request报文</li>
<li>V1TRAP：SNMPv1的trap报文</li>
<li>GETBULK：SNMPv2c/v3的get-bulk-request报文</li>
<li>INFORM：SNMPv2c/v3的inform报文</li>
<li>TRAP：SNMPv2c/v3的trap报文</li>
<li>NOTIFICATION：SNMPv2c/v3的notification报文</li>
<li>REPORT：SNMPv3的report报文</li>
</ol>
<p>PDU类提供以下重要方法：</p>
<ol>
<li>add()、addAll()：把变量绑定添加到当前PDU</li>
<li>addOID()、addAllOIDs()：把变量绑定添加到当前PDU，只添加变量名（OID），值置NULL</li>
<li>get()：得到当前PDU指定索引上的变量绑定</li>
<li>set()：设置当前PDU指定索引上的变量绑定</li>
<li>remove()：移除当前PDU指定索引上的变量绑定</li>
<li>clear()：移除所有变量绑定，设置请求ID为0，可以用来重用PDU对象</li>
<li>size()：当前PDU包含的变量绑定的个数</li>
<li>getVariableBindings()：得到所有变量绑定的集合</li>
<li>trim()：移除最后一个变量绑定，如果至少存在一个绑定</li>
<li>setErrorStatus()：设置PDU的错误状态字段</li>
<li>setErrorIndex()：设置PDU的错误索引，1表示第一个变量绑定存在错误</li>
<li>isConfirmedPdu()：判断该PDU是否是需要确认的PDU（非report、trap、response）</li>
<li>setType()：设置PDU类型</li>
<li>setRequestID()：设置请求ID，如果设置为0或者空，发送时消息处理模型自动生成</li>
</ol>
<p> PDU包含以下子类型：</p>
<ol>
<li>PDUv1：表示SNMPv1的PDU</li>
<li>ScopedPDU：表示SNMPv3的scoped PDU</li>
</ol>
<p>注意对于SNMPv2c，直接使用PDU类</p>
<p>下面的代码示例了如何创建一个SNMPv3的PDU：</p>
<pre class="crayon-plain-tag">PDU pdu = new ScopedPDU();
pdu.add( new VariableBinding( new OID( "1.3.6" ) ) );
pdu.setType( PDU.GETNEXT );</pre>
</td>
</tr>
<tr>
<td>Target</td>
<td>
<p>目标，该接口代表远程（即对端）SNMP实体（Manager、Agent）。定义了以下方法：
<ol>
<li>setAddress()：设置目标的地址</li>
<li>setVersion()：设置目标的SNMP版本号（org.snmp4j.mp.SnmpConstants）</li>
<li>setRetries()：设置请求超时前重试的次数</li>
<li>setTimeout()：设置请求超时的毫秒数</li>
<li>setMaxSizeRequestPDU()：目标支持的最大PDU长度</li>
<li>对应的getter方法</li>
</ol>
<p>该接口的缺省实现是AbstractTarget，并且包含几个子类型，这些子类型是按照安全性特征划分的：</p>
<ol>
<li>CommunityTarget：SNMPv1、SNMPv2c的基于团体名的消息处理模型的目标</li>
<li>SecureTarget：与安全模型独立的目标超类，用来支持安全的SNMP通信。包含子类
<ol>
<li>UserTarget：用于SNMPv3的，基于用户的目标</li>
</ol>
</li>
</ol>
<p>下面是创建目标的样例代码：</p>
<pre class="crayon-plain-tag">/* SNMPv3基于用户的target */
UserTarget target = new UserTarget();
target.setAddress( targetAddress );
target.setRetries( 1 ); //重试次数
target.setTimeout( 5000 ); //请求超时
target.setVersion( SnmpConstants.version3 );//SNMP版本
//安全级别
target.setSecurityLevel( SecurityLevel.AUTH_PRIV );
//用户名
target.setSecurityName( new OctetString( "ALEX" ) );</pre>
</td>
</tr>
<tr>
<td>ResponseEvent</td>
<td>
<p>应答事件类，该类继承了JDK的观察者模式组件。应答事件将一个请求PDU和它的响应，以及一个可选的用户对象进行关联。它提供以下方法：
<ol>
<li>getRequest()：得到请求PDU</li>
<li>getResponse()：得到响应PDU</li>
<li>getUserObject()：对于异步请求，可读写一个可选的用户自定义对象</li>
<li>getError()：如果请求PDU处理出错，可以从中得到异常对象</li>
<li>getPeerAddress()：得到对端的地址，即应答发送方的地址</li>
</ol>
<p>下面的代码示例如何使用该类：</p>
<pre class="crayon-plain-tag">//发送请求PDU后，可以得到应答事件对象
ResponseEvent response = snmp.send( pdu, target );
//得到应答PDU
PDU responsePDU = response.getResponse();
Address peerAddress = response.getPeerAddress();</pre>
</td>
</tr>
<tr>
<td>ResponseListener</td>
<td>
<p>这是一个回调接口，实现它可以异步的处理应答PDU：
<pre class="crayon-plain-tag">//捕获trap并打印的例子：
ResponseListener listener = new ResponseListener() {
   public void onResponse( ResponseEvent event )
   {
      //当收到响应时，应当总是取消异步请求，否则会导致内存泄漏
      //将请求发送到广播地址时，不立即取消请求
      ((Snmp) event.getSource()).cancel( event.getRequest(),this);
      System.out.println( "Resp PDU is: " + event.getResponse());
   }
};
snmp.send( pdu, target, null, listener );</pre>
</td>
</tr>
<tr>
<td>CommandResponderEvent</td>
<td>
<p>该事件由MessageDispatcher负责分发，相应的CommandResponder会接收到并处理与该事件关联的request、report、trap/notification类型的PDU。
<p>该类提供以下方法：</p>
<ol>
<li>getSecurityModel()：得到使用的安全模型</li>
<li>getSecurityLevel()：得到安全级别</li>
<li>getPDU()：得到PDU</li>
<li>isProcessed()：事件是否已经被处理</li>
<li>getPeerAddress()：得到对端SNMP实体的地址</li>
</ol>
</td>
</tr>
<tr>
<td>CommandResponder</td>
<td>
<p>该接口用来处理入站request、report、notification(trap)类型的PDU：</p>
<pre class="crayon-plain-tag">CommandResponder trapPrinter = new CommandResponder() {
    public synchronized void processPdu( CommandResponderEvent e )
    {
        PDU command = e.getPDU();
        if ( command != null )
        {
            System.out.println( command.toString() );
        }
        //每个命令只能被处理一次，因此再处理完毕后必须设置下面的标记
        e.setProcessed( true );
    }
};
snmp.addCommandResponder( trapPrinter );</pre>
</td>
</tr>
<tr>
<td>MessageDispatcher</td>
<td>
<p>消息分发器接口，处理incoming消息并将消息分发给感兴趣的CommandResponder，同时支持发送outgoing消息。
<p>消息分发器至少需要一个TransportMapping、一个MessageProcessingModel对象，以支持处理任何消息。该接口包含两个实现类：</p>
<ol>
<li>MessageDispatcherImpl：使用MessageProcessingModel解码、分发incoming消息；或者使用适当的TransportMapping来编码、发送outgoing消息</li>
<li>MultiThreadedMessageDispatcher：MessageDispatcher的装饰器，用来提供多线程并行处理的支持</li>
</ol>
</td>
</tr>
<tr>
<td>Address</td>
<td>
<p>该接口对SNMP协议支持的地址类型进行抽象：</p>
<p><img class="size-full wp-image-8333 alignleft" src="https://blog.gmem.cc/wp-content/uploads/2015/09/snmp4j-address.png" alt="snmp4j-address" width="322" height="132" /></p>
</td>
</tr>
<tr>
<td>TransportMapping</td>
<td>
<p>该接口定义了传输机制，传输机制是Snmp使用的某种传输层协议的支持。常用的实现为：DefaultTcpTransportMapping、DefaultUdpTransportMapping，分别代表TCP、UDP传输。该接口定义了以下重要方法：</p>
<ol>
<li>sendMessage()：使用该传输发送消息到某一地址</li>
<li>add/removeTransportListener()：添加或删除传输监听器。一般至少有一个传输监听器被配置，以接收incoming消息</li>
<li>listen()：启动监听，等待incoming消息的到来</li>
<li>close()：关闭该传输，并释放相关的资源</li>
</ol>
<p>定义传输机制时，可以指定它的监听地址和端口：</p>
<pre class="crayon-plain-tag">//不使用默认SNMP端口
UdpAddress udpAddress = new UdpAddress( "192.168.0.89/16100" );</pre>
</td>
</tr>
<tr>
<td>TransportListener</td>
<td>
<p>该接口定义了一个方法：processMessage()，用来处理incoming消息，该接口由Snmp4j负责回调
</td>
</tr>
<tr>
<td>MessageProcessingModel</td>
<td>
<p>消息处理模型的通用方法定义：</p>
<ol>
<li>getID()：得到消息处理模型的数字常量ID</li>
<li>prepareOutgoingMessage()：准备一个RFC3412出站消息</li>
<li>prepareResponseMessage()：准备一个RFC3412应答消息</li>
<li>prepareDataElements()：准备处理来自RFC3412入站消息的数据元素</li>
<li>isProtocolVersionSupported()：检查某个SNMP协议是否被当前模型支持</li>
</ol>
<p>具体实现类包括：<br />MPv1为SNMPv1的消息处理模型<br />MPv2c为SNMPv2c的消息处理模型<br />MPv3为SNMPv3的消息处理模型</p>
</td>
</tr>
<tr>
<td>SecurityProtocols</td>
<td>
<p>管理SNMP实体支持的所有的身份验证和隐私协议，单例。定义了以下方法：</p>
<ol>
<li>addDefaultProtocols()：添加默认安全协议</li>
<li>addAuthenticationProtocol()：添加身份验证协议</li>
<li>getAuthenticationProtocol()：根据OID得到某个身份验证协议</li>
<li>removeAuthenticationProtocol()：移除某个身份验证协议</li>
<li>add/get/removePrivacyProtocol：添加、得到、删除某个隐私（加解密）协议</li>
</ol>
</td>
</tr>
<tr>
<td>AuthenticationProtocol</td>
<td>
<p>为所有SNMP身份验证协议定义统一接口</p>
</td>
</tr>
<tr>
<td>PrivacyProtocol</td>
<td>为所有SNMP隐私（即加解密）协议定义统一接口</td>
</tr>
<tr>
<td>SecurityModels</td>
<td>
<p>管理SNMP实体支持的所有安全模型，单例。定义了以下方法：</p>
<ol>
<li>addSecurityModel()：添加一个安全模型</li>
<li>removeSecurityModel()：移除一个安全模型</li>
<li>getSecurityModel()：得到一个安全模型</li>
</ol>
</td>
</tr>
<tr>
<td>SecurityModel</td>
<td>
<p>该接口映射RFC3411定义的安全模型。该接口定义了以下安全模型ID常量：</p>
<p>SECURITY_MODEL_ANY = 0   <br />SECURITY_MODEL_SNMPv1 = 1<br />SECURITY_MODEL_SNMPv2c = 2<br />SECURITY_MODEL_USM = 3</p>
<p>以及以下方法：</p>
<ol>
<li>getID()：获得当前安全模型的ID</li>
<li>newSecurityParametersInstance()：创建一个与当前模型匹配的安全参数实例</li>
<li>generateRequestMessage()：依据消息处理模型、安全参数、消息载荷来创建一个请求消息</li>
<li>generateResponseMessage()：创建应答消息</li>
<li>processIncomingMsg()：处理入站消息</li>
</ol>
<p>该接口包含一个实现类：USM，对应RFC3414定义的基于用户的安全模型（User Based Security Model）。下面的代码示例了该安全模型的使用方法：</p>
<pre class="crayon-plain-tag">//SMMPv3要求代理定义一个唯一性的引擎ID
OctetString eid = new OctetString( MPv3.createLocalEngineID() );
//创建安全模型
USM usm = new USM( SecurityProtocols.getInstance(), eid , 0 );
//添加到安全模型管理器
SecurityModels.getInstance().addSecurityModel( usm );
OctetString userName = new OctetString( "ALEX" );
OctetString pswd= new OctetString( "ALEXUserPrivPassword" );
OctetString passphrase = new OctetString( "ALEXUserAuthPassword" );
//创建用户
UsmUser user = new UsmUser( 
    userName,     // 安全性名称（用户名）
    AuthMD5.ID,   // 身份验证协议
    passphrase ,  // 身份验证短语（密码）
    PrivDES.ID,   // 隐私协议
    pswd          // 加解密短语（密码）
);
snmp.getUSM().addUser( userName, user ); //在安全模型中添加用户</pre>
</td>
</tr>
<tr>
<td>Variable</td>
<td>
<p>该接口映射SNMP管理信息结构（SMI）定义的数据类型，包含了一个实现类AbstractVariable，后者有以下子类：
<p><img class="size-full wp-image-8306 alignleft" src="https://blog.gmem.cc/wp-content/uploads/2015/09/AbstractVariables.png" alt="AbstractVariables" width="322" height="341" /></p>
</td>
</tr>
<tr>
<td>OID</td>
<td>
<p>OID是一种特殊的Variable，代表对象标识符（不是实例标识符）</p>
</td>
</tr>
<tr>
<td>VariableBinding</td>
<td>
<p>即变量名、值对，变量名即OID，变量值即Variable</p>
</td>
</tr>
<tr>
<td>BaseAgent</td>
<td>
<p>该类定义了一个框架，用户可以扩展它、覆盖其中的抽象方法来创建SNMP代理。该类定义了以下必须扩展点（抽象方法）：</p>
<ol>
<li>registerManagedObjects()：注册该代理管理的额外对象</li>
<li>unregisterManagedObjects()：解除注册该代理管理的额外对象</li>
<li>addUsmUser(USM usm)：添加所有必须的初始用户到安全模型中</li>
<li>addNotificationTargets(SnmpTargetMIB, SnmpNotificationMIB)：添加初始的notification目标和过滤器。SnmpTargetMIB提供Target的配置，SnmpNotificationMIB提供notification的配置</li>
<li>addViews(VacmMIB)：添加初始的VACM配置。VacmMIB持有代理的视图配置</li>
<li>addCommunities(SnmpCommunityMIB)：添加SNMPv1、SNMPv2c的团体名映射</li>
</ol>
<p>该类还定义了以下可选扩展点（受保护的钩子）：</p>
<ol>
<li>initConfigMIB()：初始化配置MIB</li>
<li>getContext(MOGroup)：子类覆盖后，可以指定每个MIB模块（组）被注册到的上下文</li>
<li>registerSnmpMIBs()：为代理的MOServer注册基本的MIB模块</li>
<li>unregisterSnmpMIBs()：解除注册基本的MIB模块</li>
<li>setupDefaultProxyForwarder()：创建并注册默认的代理转发器程序（proxy forwarder application）</li>
<li>addShutdownHook()：添加JRE标准的关闭钩子，钩子在收到SIGTERM信号后执行</li>
<li>finishInit()：完成代理的初始化，默认实现将：连接服务和命令处理器（CommandProcessor）、设立USM、VACM、通知目标。并最终发送coldStart消息给通知目标</li>
<li>sendColdStartNotification()：发送冷启动通知给通知目标</li>
<li>initMessageDispatcher()：初始化消息分发器</li>
<li>initSnmpSession()：初始化SNMP会话</li>
<li>updateSession(Session)：为一个会话更新所有对象，如果子类创建了非默认的SNMP MIB实例，必须覆盖此方法</li>
<li>updateEngineBoots()：更新引起启动次数计数器</li>
<li>initTransportMappings()：初始化该代理使用的传输机制</li>
</ol>
</td>
</tr>
<tr>
<td>MOServer</td>
<td>
<p>受管对象服务器，负责管理注册的ManagedObject，该接口定义了受管对象仓库必须提供给CommandResponder的服务。包含以下方法：</p>
<ol>
<li>addContextListener(ContextListener)：添加上下文监听器，当上下文被添加或者删除，监听器会被通知</li>
<li>removeContextListener(ContextListener)：移除上下文监听器</li>
<li>addContext(OctetString)：添加上下文到服务器</li>
<li>removeContext(OctetString)：移除上下文</li>
<li>register(ManagedObject, OctetString)：为指定上下文注册一个受管对象，一个受管对象可以注册到多个上下文。如果第二个参数为null，则注册到所有上下文（包括默认上下文）</li>
<li>unregister(ManagedObject, OctetString)：解除注册受管对象</li>
<li>addLookupListener()：添加查找监听器，lookup调用返回前，监听器被通知</li>
<li>removeLookupListener()：移除查找监听器</li>
<li>lookup(MOQuery)：查找第一个（字典序）匹配的受管对象</li>
<li>iterator()：返回一个迭代器，以迭代该服务器上的内容</li>
<li>lock(Object, ManagedObject)：锁定一个对象，对其进行lookup将阻塞直到解锁或超时</li>
<li>unlock(Object, ManagedObject)：解锁一个对象</li>
<li>getContexts()：获得所有上下文</li>
<li>isContextSupported(OctetString)：是否支持指定上下文</li>
</ol>
</td>
</tr>
<tr>
<td>ManagedObject</td>
<td>该接口抽象“受管对象”，一个受管对象可能对应MIB中的某个分支，因而可以包含多个OID，OID的范围由MOScope指定</td>
</tr>
<tr>
<td>MOScope</td>
<td>表示一个连续的OID区域，具有OID上下限</td>
</tr>
<tr>
<td>MOGroup</td>
<td>
<p>受管对象组，表示一组ManagedObject，可以通过<pre class="crayon-plain-tag">registerMOs(MOServer server, OctetString context)</pre> 注册到MOServer。该接口实际上对应了管理信息库（MIB）对象。包含以下实现（其中MOGroupImpl是一个简单的实现，StaticMOGroup则可以用来轻松的实现静态、只读受管对象组）：</p>
<p><img class="size-full wp-image-8363 alignleft" src="https://blog.gmem.cc/wp-content/uploads/2015/09/MOGroupImpls1.png" alt="MOGroupImpls" width="375" height="288" /></p>
</td>
</tr>
<tr>
<td>NotificationOriginator</td>
<td>
<p>通知源，实现该接口的对象负责发送notification，它定义了一个方法：</p>
<pre class="crayon-plain-tag">//发送notifications(traps)到合适的目标
//目标由SNMP-TARGET-MIB、SNMP-NOTIFICATION-MIB指定
Object notify(
    OctetString context,//通知对应的上下文
    OID notificationID, //唯一代表通知的OID
    TimeTicks sysUpTime, //上下文的系统启动时间
    VariableBinding[] vbs //通知的载荷
);</pre>
</td>
</tr>
<tr>
<td>CommandProcessor</td>
<td>
<p>BaseAgent使用的一个实现了CommandResponder、NotificationOriginator的粘合剂类，负责处理请求、发送通知
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">网络管理站程序（Manager）开发</span></div>
<div class="blog_h3"><span class="graybg">请求和批量请求操作</span></div>
<pre class="crayon-plain-tag">package cc.gmem.study.snmp;

import java.util.Vector;
import java.util.concurrent.TimeUnit;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.snmp4j.CommunityTarget;
import org.snmp4j.PDU;
import org.snmp4j.ScopedPDU;
import org.snmp4j.Snmp;
import org.snmp4j.Target;
import org.snmp4j.TransportMapping;
import org.snmp4j.UserTarget;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.event.ResponseListener;
import org.snmp4j.mp.MPv3;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.security.AuthMD5;
import org.snmp4j.security.PrivDES;
import org.snmp4j.security.SecurityLevel;
import org.snmp4j.security.SecurityModel;
import org.snmp4j.security.SecurityModels;
import org.snmp4j.security.SecurityProtocols;
import org.snmp4j.security.USM;
import org.snmp4j.security.UsmUser;
import org.snmp4j.smi.Address;
import org.snmp4j.smi.GenericAddress;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.TimeTicks;
import org.snmp4j.smi.Variable;
import org.snmp4j.smi.VariableBinding;
import org.snmp4j.transport.DefaultUdpTransportMapping;

public class Snmp4jManagerRequestTest
{
    private static Logger           LOGGER = LoggerFactory.getLogger( Snmp4jManagerRequestTest.class );

    private static Snmp             session;

    private static TransportMapping transport;

    private static Address          targetAddr;

    @BeforeClass
    public static void setUpBeforeClass() throws Exception
    {
        transport = new DefaultUdpTransportMapping();
        session = new Snmp( transport );
        targetAddr = GenericAddress.parse( "udp:192.168.0.90/161" );
        transport.listen();

        //需要在Manager的数据库中添加Agent用户的安全性信息
        SecurityProtocols instance = SecurityProtocols.getInstance();
        instance.addDefaultProtocols();
        USM usm = new USM( instance, new OctetString( MPv3.createLocalEngineID() ), 0 );
        SecurityModels.getInstance().addSecurityModel( usm );

        //添加SNMPv3的用户
        OctetString securityName = new OctetString( "snmp" );
        OctetString pswd = new OctetString( "snmppswd" );
        UsmUser user = new UsmUser( securityName, AuthMD5.ID, pswd, PrivDES.ID, pswd );
        session.getUSM().addUser( securityName, user );
    }

    @AfterClass
    public static void tearDownAfterClass() throws Exception
    {
        session.close();
        transport.close();
    }

    /**
     * 使用SNMPv2c协议发送一个同步GET请求，获取代理系统的已启动时间
     */
    @Test
    public void testSNMPv2cSyncGetRequest() throws Exception
    {
        Target target = new CommunityTarget( 
                targetAddr,
                new OctetString( "authenticated" ) //团体名
        );
        target.setVersion( SnmpConstants.version2c );

        PDU req = new PDU(); //SNMPv2c协议数据单元
        req.setType( PDU.GET );
        //注意OID必须使用实例标识，对于简单变量，实例标识就是OID.0
        req.add( new VariableBinding( new OID( "1.3.6.1.2.1.1.3.0" ) ) );
        //同步发送，直接获得返回值
        ResponseEvent respEvent = session.send( req, target );
        PDU resp = respEvent.getResponse();
        //需要此判断，很多时候（例如超时）snmp4j简单的把应答PDU设置为空指针，而缺乏必要的错误信息
        if ( resp != null )
        {
            VariableBinding vb = resp.get( 0 );
            TimeTicks val = (TimeTicks) vb.getVariable();
            LOGGER.debug( "System Uptime: {}s", val.toLong() / 100 ); //转化为秒数
        }
    }

    /**
     * 使用SNMPv3协议发送一个异步GET请求，获取代理系统的已启动时间
     */
    @Test
    public void testSNMPv3ASyncGetRequest() throws Exception
    {
        UserTarget target = new UserTarget();
        target.setAddress( targetAddr );
        target.setVersion( SnmpConstants.version3 );
        target.setTimeout( 1000 );
        target.setRetries( 3 );

        target.setSecurityName( new OctetString( "snmp" ) );
        target.setSecurityLevel( SecurityLevel.AUTH_PRIV );
        target.setSecurityModel( SecurityModel.SECURITY_MODEL_USM );

        ScopedPDU req = new ScopedPDU(); //SNMPv3协议数据单元
        req.setType( PDU.GET );
        req.add( new VariableBinding( new OID( "1.3.6.1.2.1.1.3.0" ) ) );
        session.send( req, target, null, new ResponseListener() {

            public void onResponse( ResponseEvent event )
            {
                ( (Snmp) event.getSource() ).cancel( event.getRequest(), this );
                TimeTicks val = (TimeTicks) event.getResponse().get( 0 ).getVariable();
                LOGGER.debug( "System Uptime: {}s", val.toLong() / 100 );
            }
        } );
        TimeUnit.SECONDS.sleep( 1 ); //防止JUnit过早的退出
    }

    /**
     * 使用SNMPv2c协议执行GET-BULK请求，获得代理的UDP监听表格
     */
    @SuppressWarnings ( "unchecked" )
    @Test
    public void testSNMPv2cGetBulkRequest() throws Exception
    {
        Target target = new CommunityTarget( targetAddr, new OctetString( "authenticated" ) );
        target.setVersion( SnmpConstants.version2c );

        PDU req = new PDU();
        req.setType( PDU.GETBULK );
        //批量请求最大重复变量个数，注意，应答PDU在这里受限于单个UDP数据报的大小，因此不能设置的过大
        //单个UDP数据报的绝对限制是65535字节，此外还收到MTU的限制，可能只有1500字节
        req.setMaxRepetitions( 100 );
        req.setNonRepeaters( 0 );
        req.add( new VariableBinding( new OID( "1.3.6.1.2.1.7.5" ) ) );
        ResponseEvent respEvent = session.send( req, target );
        PDU resp = respEvent.getResponse();
        if ( resp != null &amp;&amp; resp.getErrorStatus() == PDU.noError )
        {
            Vector&lt;? extends VariableBinding&gt; vbs = resp.getVariableBindings();
            StringBuilder buf = new StringBuilder();
            for ( VariableBinding vb : vbs )
            {
                Variable var = vb.getVariable();
                String oid = vb.getOid().toString();
                if ( !oid.startsWith( "1.3.6.1.2.1.7.5.1" ) ) break; //超过UDP监听表格的范围
                buf.append( String.format( "%s: (%s)%s\n", vb.getOid(), var.getSyntaxString(), var ) );
            }
            LOGGER.debug( buf.toString() );
        }
    }
}</pre>
<div class="blog_h3"><span class="graybg">陷阱/通知监听</span></div>
<pre class="crayon-plain-tag">public class Snmp4jManagerTrapTest
{
    private static Logger           LOGGER        = LoggerFactory.getLogger( Snmp4jManagerTrapTest.class );

    private static Snmp             session;

    private static TransportMapping transport;

    private static ThreadPool       threadPool;

    private Lock                    exitLock      = new ReentrantLock();

    private Condition               exitCondition = exitLock.newCondition();

    @BeforeClass
    public static void setUpBeforeClass() throws Exception
    {
        UdpAddress localAddr = (UdpAddress) GenericAddress.parse( "udp:0.0.0.0/162" );//Manager的监听地址
        transport = new DefaultUdpTransportMapping( localAddr );
        threadPool = ThreadPool.create( "TrapReceiver", 16 );//创建16线程的池
        //创建多线程的报文分发器
        MessageDispatcher dispatcher = new MultiThreadedMessageDispatcher( threadPool, new MessageDispatcherImpl() );
        dispatcher.addMessageProcessingModel( new MPv2c() ); //添加消息处理模型支持
        dispatcher.addMessageProcessingModel( new MPv3() ); //添加消息处理模型支持
        session = new Snmp( dispatcher, transport );
        transport.listen();

        SecurityProtocols instance = SecurityProtocols.getInstance();
        instance.addDefaultProtocols();
        USM usm = new USM( instance, new OctetString( MPv3.createLocalEngineID() ), 0 );
        SecurityModels.getInstance().addSecurityModel( usm );
        OctetString securityName = new OctetString( "user" );
        OctetString pswd = new OctetString( "password" );
        UsmUser user = new UsmUser( securityName, AuthMD5.ID, pswd, PrivDES.ID, pswd );
        session.getUSM().addUser( securityName, user );
    }

    @AfterClass
    public static void tearDownAfterClass() throws Exception
    {
        session.close();
        transport.close();
        threadPool.cancel();
    }


    @Test
    public void testTrapAndNotification() throws Exception
    {
        session.addCommandResponder( new CommandResponder() {
            private volatile int mscCount;

            @SuppressWarnings ( "unchecked" )
            public void processPdu( CommandResponderEvent event )
            {
                String community = new String( event.getSecurityName() );
                int secmod = event.getSecurityModel();
                if ( secmod == SECURITY_MODEL_SNMPv2c )
                {
                    //基于团体名的身份验证
                    if ( !"public".equals( community ) )
                    {
                        LOGGER.error( "Authentication failed." );
                        return;
                    }

                }
                else if ( secmod == SECURITY_MODEL_USM )
                {
                    //不用管，snmp4j负责身份验证，如果失败，CommandResponder不会接收到报文
                }
                else
                {
                    LOGGER.error( "Unsupported security model: {}", secmod );
                    return;
                }
                PDU command = event.getPDU();
                if ( command.getType() == PDU.TRAP )
                {
                    Vector&lt;? extends VariableBinding&gt; vbs = command.getVariableBindings();
                    StringBuilder buf = new StringBuilder();
                    for ( VariableBinding vb : vbs )
                    {
                        Variable var = vb.getVariable();
                        buf.append( String.format( "%s: (%s)%s\n", vb.getOid(), var.getSyntaxString(), var ) );
                    }
                    LOGGER.debug( buf.toString() );
                    event.setProcessed( true ); //设置事件的已处理状态
                }
                else if ( command.getType() == ScopedPDU.NOTIFICATION )
                {
                    
                }
                else
                {
                    return;
                }
                mscCount++;
                if ( mscCount == 6 ) //等待Agent发送6个报文
                {
                    exitLock.lock();
                    exitCondition.signalAll();
                    exitLock.unlock();
                }
            }
        } );
        exitLock.lock();
        exitCondition.await();
        exitLock.unlock();
    }
}</pre>
<div class="blog_h2"><span class="graybg">被管网络单元程序（Agent）开发</span></div>
<div class="blog_h3"><span class="graybg">发送陷阱/通知</span></div>
<pre class="crayon-plain-tag">public class Snmp4jAgentTrapTest
{
    private static final String     TRAP_OID = "1.3.6.1.4.1.3835.1.1";

    private static final String     MSG_OID  = "1.3.6.1.4.1.3835.1.3";

    private static Logger           LOGGER   = LoggerFactory.getLogger( Snmp4jAgentTrapTest.class );

    private static Snmp             session;

    private static Address          targetAddr;

    private static TransportMapping transport;

    @BeforeClass
    public static void setUpBeforeClass() throws Exception
    {
        UdpAddress localAddr = (UdpAddress) GenericAddress.parse( "udp:0.0.0.0/161" );//Agent的监听地址
        targetAddr = GenericAddress.parse( "udp:127.0.0.1/162" ); //Manager的监听地址
        transport = new DefaultUdpTransportMapping( localAddr );
        session = new Snmp( transport );
        transport.listen();

        SecurityProtocols instance = SecurityProtocols.getInstance();
        instance.addDefaultProtocols();
        USM usm = new USM( instance, new OctetString( MPv3.createLocalEngineID() ), 0 );
        SecurityModels.getInstance().addSecurityModel( usm );
        OctetString securityName = new OctetString( "user" );
        OctetString pswd = new OctetString( "password" );
        UsmUser user = new UsmUser( securityName, AuthMD5.ID, pswd, PrivDES.ID, pswd );
        session.getUSM().addUser( securityName, user );
    }

    @AfterClass
    public static void tearDownAfterClass() throws Exception
    {
        session.close();
        transport.close();
    }

    /**
     * 发送SNMPv2c的TRAP通知给Manager
     */
    @Test
    public void testSNMPv2cTrap() throws Exception
    {

        CommunityTarget target = new CommunityTarget();
        target.setAddress( targetAddr );
        target.setCommunity( new OctetString( "public" ) ); //Manager认可的团体名
        target.setVersion( SnmpConstants.version2c );
        target.setTimeout( 3000 );

        PDU trap = DefaultPDUFactory.createPDU( SnmpConstants.version2c );
        trap.setType( PDU.TRAP ); //对于SNMPv1，需要使用PDU.V1TRAP类型

        for ( int i = 0; i &lt; 5; i++ )
        {
            trap.clear();
            trap.add( new VariableBinding( SnmpConstants.sysUpTime, new TimeTicks( 360000L ) ) );
            trap.add( new VariableBinding( SnmpConstants.snmpTrapOID, new OID( TRAP_OID ) ) );
            trap.add( new VariableBinding( SnmpConstants.snmpTrapAddress, new IpAddress( "192.168.0.89" ) ) );
            String msg = "Trapped " + i;
            trap.add( new VariableBinding( new OID( MSG_OID ), new OctetString( msg ) ) );
            session.send( trap, target );
            LOGGER.debug( "TRAP sent: {}", msg );
            TimeUnit.SECONDS.sleep( 1 );
        }
    }

    /**
     * 发送SNMPv3的NOTIFICATION通知给Manager
     */
    @Test
    public void testSNMPv3Notification() throws Exception
    {
        UserTarget target = new UserTarget();
        target.setAddress( targetAddr );
        target.setVersion( SnmpConstants.version3 );
        target.setSecurityName( new OctetString( "user" ) );
        target.setSecurityLevel( SecurityLevel.AUTH_PRIV );
        target.setSecurityModel( SecurityModel.SECURITY_MODEL_USM );

        ScopedPDU notify = new ScopedPDU();
        notify.setType( ScopedPDU.NOTIFICATION ); //使用SNMPv2c/SNMPv3的通知方式

        notify.add( new VariableBinding( SnmpConstants.sysUpTime, new TimeTicks( 360000L ) ) );
        notify.add( new VariableBinding( SnmpConstants.snmpTrapOID, SnmpConstants.linkDown ) );
        String msg = "Notified";
        notify.add( new VariableBinding( new OID( TRAP_OID ), new OctetString( msg ) ) );
        session.send( notify, target );
        LOGGER.debug( "NOTIFICATION sent: {}", msg );
        TimeUnit.SECONDS.sleep( 1 );
    }
}</pre>
<div class="blog_h3"><span class="graybg">应答请求</span></div>
<pre class="crayon-plain-tag">public class Snmp4jAgentResponderTest
{
    private static Logger           LOGGER        = LoggerFactory.getLogger( Snmp4jAgentResponderTest.class );

    private static Snmp             session;

    private static ThreadPool       threadPool;

    private static TransportMapping transport;

    private Lock                    exitLock      = new ReentrantLock();

    private Condition               exitCondition = exitLock.newCondition();

    @BeforeClass
    public static void setUpBeforeClass() throws Exception
    {
        UdpAddress localAddr = (UdpAddress) GenericAddress.parse( "udp:0.0.0.0/161" );//Agent的监听地址
        transport = new DefaultUdpTransportMapping( localAddr );
        threadPool = ThreadPool.create( "TrapReceiver", 16 );//创建16线程的池
        //创建多线程的报文分发器
        MessageDispatcher dispatcher = new MultiThreadedMessageDispatcher( threadPool, new MessageDispatcherImpl() );
        dispatcher.addMessageProcessingModel( new MPv2c() ); //添加消息处理模型支持
        dispatcher.addMessageProcessingModel( new MPv3() ); //添加消息处理模型支持
        session = new Snmp( dispatcher, transport );
        transport.listen();
    }

    @AfterClass
    public static void tearDownAfterClass() throws Exception
    {
        session.close();
        transport.close();
        threadPool.cancel();
    }

    /**
     * 处理GET请求并返回应答
     */
    @Test
    public void testSNMPv2cResponse() throws Exception
    {
        //和TRAP接收方一样，请求接收方同样使用CommandResponder回调接口
        //需要注意的是，为了提升性能，一般需要使用MultiThreadedMessageDispatcher
        session.addCommandResponder( new CommandResponder() {

            public void processPdu( CommandResponderEvent event )
            {
                //这里可以限定请求使用的安全模型，并进行身份验证

                PDU command = event.getPDU();
                if ( PDU.GET == command.getType() )
                {
                    VariableBinding vb = command.get( 0 );
                    OID oid = vb.getOid();
                    command.setErrorIndex( 0 );
                    command.setErrorStatus( 0 );
                    command.setType( PDU.RESPONSE );
                    //模拟一个应答值，正常业务逻辑中，这里可以调用某种服务获得对象的值
                    String val = "value.of." + oid;
                    command.get( 0 ).setVariable( new OctetString( val ) );
                    try
                    {
                        MessageDispatcher dispatcher = event.getMessageDispatcher();
                        //发送应答报文
                        dispatcher.returnResponsePdu(
                                event.getMessageProcessingModel(),
                                event.getSecurityModel(),
                                //这里我们假设使用团体名的验证方式
                                new OctetString( "public" ).getValue(),
                                event.getSecurityLevel(),
                                command,
                                event.getMaxSizeResponsePDU(),
                                event.getStateReference(),
                                new StatusInformation() );
                        LOGGER.debug( "Responded {} to {}", val, oid );

                    }
                    catch ( MessageException e )
                    {
                        LOGGER.debug( "Failed to send response PDU:", e );
                    }
                    finally
                    {
                        event.setProcessed( true );
                        exitLock.lock();
                        exitCondition.signalAll();
                        exitLock.unlock();
                    }
                }
            }
        } );
        exitLock.lock();
        exitCondition.await();
        exitLock.unlock();
    }

}</pre>
<p>在Ubuntu下做测试：</p>
<pre class="crayon-plain-tag">snmpget -v 2c -c public -l authNoPriv  192.168.0.89 1.3.6.1.4.1.3835.1.3
#输出：
#SNMPv2-SMI::enterprises.3835.1.3 = STRING: "value.of.1.3.6.1.4.1.3835.1.3"</pre>
<div class="blog_h3"><span class="graybg">Agent服务器</span></div>
<p>继承BaseAgent可以开发自己的Agent服务器，下面是一个简单的Agent服务器：</p>
<pre class="crayon-plain-tag">package cc.gmem.study.snmp;

import java.io.File;
import java.io.IOException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.snmp4j.TransportMapping;
import org.snmp4j.agent.BaseAgent;
import org.snmp4j.agent.CommandProcessor;
import org.snmp4j.agent.DuplicateRegistrationException;
import org.snmp4j.agent.MOGroup;
import org.snmp4j.agent.ManagedObject;
import org.snmp4j.agent.mo.MOTableRow;
import org.snmp4j.agent.mo.snmp.RowStatus;
import org.snmp4j.agent.mo.snmp.SnmpCommunityMIB;
import org.snmp4j.agent.mo.snmp.SnmpNotificationMIB;
import org.snmp4j.agent.mo.snmp.SnmpTargetMIB;
import org.snmp4j.agent.mo.snmp.VacmMIB;
import org.snmp4j.agent.security.MutableVACM;
import org.snmp4j.log.Log4jLogFactory;
import org.snmp4j.log.LogFactory;
import org.snmp4j.mp.MPv3;
import org.snmp4j.security.USM;
import org.snmp4j.smi.Address;
import org.snmp4j.smi.GenericAddress;
import org.snmp4j.smi.Integer32;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.Variable;
import org.snmp4j.transport.TransportMappings;

import static org.snmp4j.security.SecurityLevel.*;
import static org.snmp4j.agent.mo.snmp.StorageType.*;
import static org.snmp4j.security.SecurityModel.*;

public class SnmpAgent extends BaseAgent
{
    private static final Logger      LOGGER    = LoggerFactory.getLogger( SnmpAgent.class );

    private static final OctetString ENGINE_ID = new OctetString( MPv3.createLocalEngineID() );
    static
    {
        //配置使用的日志系统
        LogFactory.setLogFactory( new Log4jLogFactory() );
    }

    private String                   address;

    /**
     * @param address 代理的监听地址，例如：udp:0.0.0.0/161
     */
    public SnmpAgent( String address ) throws IOException
    {
        //bootCounter.agent：存放启动计数器的文件，如果文件不存在会在代理关闭时自动创建
        //conf.agent：存放代理配置信息的文件，如果文件不存在会在代理关闭时自动创建
        super( new File( "bootCounter.agent" ), new File( "conf.agent" ), new CommandProcessor( ENGINE_ID ) );
        this.address = address;
    }

    /**
     * 不注册任何额外的受管对象
     */
    @Override
    protected void registerManagedObjects()
    {
    }

    /**
     * 注册一个受管对象到服务器的默认上下文
     * @param mo 受管对象
     */
    public void registerManagedObject( ManagedObject mo )
    {
        try
        {
            server.register( mo, null );
        }
        catch ( DuplicateRegistrationException ex )
        {
            LOGGER.error( "Registration failed: ", ex );
        }
    }

    public void unregisterManagedObject( MOGroup moGroup )
    {
        moGroup.unregisterMOs( server, getContext( moGroup ) );
    }

    @Override
    protected void addNotificationTargets( SnmpTargetMIB targetMIB, SnmpNotificationMIB notificationMIB )
    {
    }

    /**
     * 配置基于视图（View）的访问控制
     */
    @Override
    protected void addViews( VacmMIB vacm )
    {
        //把安全模型v2c和安全名称cpublic的组合添加到组
        OctetString grp = new OctetString( "group" );
        vacm.addGroup( SECURITY_MODEL_SNMPv2c, new OctetString( "cpublic" ), grp, nonVolatile );
        //为组添加访问权限
        OctetString readView = new OctetString( "fullReadView" );
        OctetString writeView = new OctetString( "fullWriteView" );
        OctetString notifyView = new OctetString( "fullNotifyView" );
        vacm.addAccess(
                grp, new OctetString( "public" ), //上下文（前缀）
                SECURITY_MODEL_ANY, //安全模型
                NOAUTH_NOPRIV, //安全级别
                MutableVACM.VACM_MATCH_EXACT, //上上下文名称完全匹配，VACM_MATCH_PREFIX表示前缀匹配
                readView, //用于读访问的视图名称（使用空串表示禁止访问）
                writeView,//用于写访问的视图名称（使用空串表示禁止访问）
                notifyView,//用于通知访问的视图名称（使用空串表示禁止访问）
                nonVolatile ); //存储类型

        //添加一个视图到VACM，前两个参数一样的既有条目被静默的覆盖
        vacm.addViewTreeFamily(
                readView, new OID( "1.3" ), //子树的OID
                new OctetString(), //掩码
                VacmMIB.vacmViewIncluded, //表示该子树被包含在MIB View中，vacmViewExcluded表示排除
                nonVolatile );
    }

    /**
     * 添加SNMPv3用户
     */
    protected void addUsmUser( USM usm )
    {
    }

    /**
     * 初始化传输机制
     */
    protected void initTransportMappings() throws IOException
    {
        transportMappings = new TransportMapping[1];
        Address addr = GenericAddress.parse( address );
        TransportMapping tm = TransportMappings.getInstance().createTransportMapping( addr );
        transportMappings[0] = tm;
    }

    /**
     * 启动代码
     */
    public void start() throws IOException
    {

        init();
        addShutdownHook();
        //添加一个上下文到受管对象服务器
        getServer().addContext( new OctetString( "public" ) );
        finishInit();
        run();
        sendColdStartNotification();
    }

    protected void unregisterManagedObjects()
    {
    }

    /**
     * 在代理的Local Configuration Datastore中添加团体名字符串的表
     * 这里仅仅配置一行：public上下文中的public团体名
     */
    protected void addCommunities( SnmpCommunityMIB communityMIB )
    {
        Variable[] com2sec = new Variable[] {
                new OctetString( "public" ), // 团体名
                new OctetString( "cpublic" ), // 安全名称
                getAgent().getContextEngineID(), // 上下文引擎
                new OctetString( "public" ), // 默认上下文
                new OctetString(), // 传输标记
                new Integer32( nonVolatile ), // 存储类型
                new Integer32( RowStatus.active ) // 该行数据的状态
        };

        MOTableRow row = communityMIB.getSnmpCommunityEntry().createRow(
                new OctetString( "public2public" ).toSubIndex( true ),//索引OID
                com2sec //列详情
                );
        communityMIB.getSnmpCommunityEntry().addRow( row );
    }
}</pre>
<p>下面的测试用例说明了如何注册标量、表格到Agent服务器，并通过GET/GETBULK请求进行验证：</p>
<pre class="crayon-plain-tag">@FixMethodOrder ( MethodSorters.JVM )
public class AgentTest
{

    private static final String OID_SYS_DESCR = "1.3.6.1.2.1.1.1.0";

    private static Logger       LOGGER        = LoggerFactory.getLogger( AgentTest.class );

    private static final String AGENT_ADDR    = "udp:127.0.0.1/16100";

    private static final String MANAGER_ADDR  = "udp:127.0.0.1/16200";

    private static UdpAddress   agentAddr     = (UdpAddress) GenericAddress.parse( AGENT_ADDR );

    private static UdpAddress   mgrAddr       = (UdpAddress) GenericAddress.parse( MANAGER_ADDR );

    private static SnmpAgent    agent;

    private static Snmp         mgrSession;

    @BeforeClass
    public static void setUpBeforeClass() throws Exception
    {
        agent = new SnmpAgent( AGENT_ADDR );
        agent.start();

        TransportMapping transportMapping = new DefaultUdpTransportMapping( mgrAddr );
        mgrSession = new Snmp( transportMapping );
        transportMapping.listen();


    }

    @AfterClass
    public static void tearDownAfterClass() throws Exception
    {
        agent.stop();
        mgrSession.close();
    }

    private Target createSNMPv2Target()
    {
        Target target = new CommunityTarget( agentAddr, new OctetString( "public" ) );
        target.setVersion( SnmpConstants.version2c );
        target.setAddress( agentAddr );
        return target;
    }

    private PDU createSNMPv2PDU_GET( String oid )
    {
        PDU req = new PDU();
        req.setType( PDU.GET );
        req.add( new VariableBinding( new OID( oid ) ) );
        return req;
    }

    private PDU createSNMPv2PDU_GETBULK( String oid, int maxRepetitions )
    {
        PDU req = new PDU();
        req.setType( PDU.GETBULK );
        req.setMaxRepetitions( maxRepetitions );
        req.setNonRepeaters( 0 );
        req.add( new VariableBinding( new OID( oid ) ) );
        return req;
    }

    private String getAsString( String oid ) throws Exception
    {
        ResponseEvent respEvent = mgrSession.send( createSNMPv2PDU_GET( oid ), createSNMPv2Target() );
        PDU resp = respEvent.getResponse();
        return resp.get( 0 ).getVariable().toString();
    }

    private String variableToString( Variable var ) throws UnsupportedEncodingException
    {
        if ( var instanceof OctetString )
        {
            return new String( ( (OctetString) var ).getValue() );
        }
        else
        {
            return var.toString();
        }
    }

    /**
     * BaseAgent注册了一些默认的MIB，我们这里发起一个1.3.6.1.2.1.1.1请求（sysDescr）测试其是否正常工作
     */
    @Test
    public void testDefaultMIB() throws Exception
    {
        //测试BaseAgent注册的默认MIB-II
        assertEquals( "SNMP4J-Agent - Windows 7 - amd64 - 6.1", getAsString( OID_SYS_DESCR ) );
    }

    /**
     * 创建一个标量并且注册到Agent
     */
    @Test
    public void testUserDefinedScalar() throws Exception
    {
        //解除注册默认的Snmpv2 MIB
        agent.unregisterManagedObject( agent.getSnmpv2MIB() );
        String sysDescr = "Ubuntu 14 - amd64";
        MOScalar mo = new MOScalar( //创建一个标量
                new OID( OID_SYS_DESCR ), //OID
                MOAccessImpl.ACCESS_READ_ONLY,//最高访问级别
                new OctetString( sysDescr ) //值
                );
        agent.registerManagedObject( mo );
        assertEquals( sysDescr, getAsString( OID_SYS_DESCR ) );
    }

    /**
     * 创建一个表格并且注册到Agent，该用例将打印：
     * 1.3.6.1.4.1.8808.8.1.1: 19
     * 1.3.6.1.4.1.8808.8.1.2: 20
     * 1.3.6.1.4.1.8808.8.2.1: 汪震
     * 1.3.6.1.4.1.8808.8.2.2: 汪静好
     * 1.3.6.1.4.1.8808.8.3.1: 198609
     * 1.3.6.1.4.1.8808.8.3.2: 201411
     * 
     * 可以看到SNMP表这种按列遍历的顺序以及Table中变量OID的组织方式
     */
    @SuppressWarnings ( "unchecked" )
    @Test
    public void testUserDefinedTable() throws Exception
    {
        final String STAFF_TAB_OID = "1.3.6.1.4.1.8808.8";
        OID staffTableOID = new OID( STAFF_TAB_OID ); //员工表
        MOTableSubIndex[] subIndexes = new MOTableSubIndex[] { new MOTableSubIndex( SMIConstants.SYNTAX_INTEGER ) };
        MOColumn[] columns = new MOColumn[] {
                new MOColumn( 1, SMIConstants.SYNTAX_INTEGER, MOAccessImpl.ACCESS_READ_ONLY ),//工号
                new MOColumn( 2, SMIConstants.SYNTAX_OCTET_STRING, MOAccessImpl.ACCESS_READ_ONLY ),//姓名
                new MOColumn( 3, SMIConstants.SYNTAX_INTEGER, MOAccessImpl.ACCESS_READ_ONLY ) //出生年月
        };
        DefaultMOTable table = new DefaultMOTable( staffTableOID, new MOTableIndex( subIndexes, false ), columns );
        MOMutableTableModel model = (MOMutableTableModel) table.getModel();
        model.addRow( new DefaultMOMutableRow2PC( new OID( "1" ), new Variable[] {
                new Integer32( 19 ), new OctetString( "汪震" ), new Integer32( 198609 )
        } ) );
        model.addRow( new DefaultMOMutableRow2PC( new OID( "2" ), new Variable[] {
                new Integer32( 20 ), new OctetString( "汪静好" ), new Integer32( 201411 )
        } ) );
        table.setVolatile( true );
        agent.registerManagedObject( table );

        PDU req = createSNMPv2PDU_GETBULK( STAFF_TAB_OID, 6 );
        ResponseEvent respEvent = mgrSession.send( req, createSNMPv2Target() );
        PDU resp = respEvent.getResponse();
        if ( resp != null &amp;&amp; resp.getErrorStatus() == PDU.noError )
        {
            Vector&lt;? extends VariableBinding&gt; vbs = resp.getVariableBindings();
            StringBuilder buf = new StringBuilder();
            for ( VariableBinding vb : vbs )
            {
                Variable var = vb.getVariable();
                buf.append( String.format( "%s: %s\n", vb.getOid(), variableToString( var ) ) );
            }
            LOGGER.debug( buf.toString() );
        }
    }
}</pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/snmp4j-study-note">snmp4j学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/snmp4j-study-note/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>SNMP协议学习笔记</title>
		<link>https://blog.gmem.cc/snmp-study-note</link>
		<comments>https://blog.gmem.cc/snmp-study-note#comments</comments>
		<pubDate>Fri, 11 May 2012 07:01:54 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Network]]></category>
		<category><![CDATA[SNMP]]></category>
		<category><![CDATA[学习笔记]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=8260</guid>
		<description><![CDATA[<p>基础知识 SNMP是用于管理网络中设备的一种应用层协议，它使用简单的request/response通信模型。被管理的设备可能包括：路由器、终端服务器、打印机等，这些设备的共同特点是运行TCP/IP协议族。SNMP协议为所有这些设备定义了统一的访问接口，简化了网络管理工作。 基于SNMP的网络管理，包含的节点分为两种类型： 网络管理站：管理站一般都是带有监视器的工作站，可以显示所有被管设备的状态（Manager） 被管网络单元（受管设备），被管设备上运行代理程序（Agent） 管理进程和代理进程之间的通信可以有两种方式： 管理进程向代理进程发出请求，询问一个具体的变量值，或者修改某一变量值（Get/Set） 代理进程主动向管理进程报告有某些重要的事件发生（Trap） 基于SNMP的网络管理，需要关注以下三个方面的内容： 管理信息结构（SMI）：关于MIB的一套公用的结构和表示符号（数据类型）。RFC 1155定义了SMI 管理信息库（MIB）：管理信息库包含所有代理进程的所有可被查询和修改的变量。RFC1213定义了第二版MIB，称为MIB-II 简单网络管理协议（SNMP）：管理进程和代理进程之间的通信协议 管理信息结构 SNMP中定义了以下数据类型（其中全大写的是来自ASN.1的内建类型）： 数据类型  说明 INTEGER 整数类型。某些变量还具有额外的范围限制，例如UDP端口号必须在0-65535之间 OCTET STRING 0或者多个8bit的字节构成的字符串，每个字节的值在0-255之间 <a class="read-more" href="https://blog.gmem.cc/snmp-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/snmp-study-note">SNMP协议学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">基础知识</span></div>
<p>SNMP是用于管理网络中设备的一种应用层协议，它使用简单的request/response通信模型。被管理的设备可能包括：路由器、终端服务器、打印机等，这些设备的共同特点是运行TCP/IP协议族。SNMP协议为所有这些设备定义了统一的访问接口，简化了网络管理工作。</p>
<p>基于SNMP的网络管理，包含的节点分为两种类型：</p>
<ol>
<li>网络管理站：管理站一般都是带有监视器的工作站，可以显示所有被管设备的状态（Manager）</li>
<li>被管网络单元（受管设备），被管设备上运行代理程序（Agent）</li>
</ol>
<p>管理进程和代理进程之间的通信可以有两种方式：</p>
<ol>
<li>管理进程向代理进程发出请求，询问一个具体的变量值，或者修改某一变量值（Get/Set）</li>
<li>代理进程主动向管理进程报告有某些重要的事件发生（Trap）</li>
</ol>
<p>基于SNMP的网络管理，需要关注以下三个方面的内容：</p>
<ol>
<li>管理信息结构（SMI）：关于MIB的一套公用的结构和表示符号（数据类型）。RFC 1155定义了SMI</li>
<li>管理信息库（MIB）：管理信息库包含所有代理进程的所有可被查询和修改的变量。RFC1213定义了第二版MIB，称为MIB-II</li>
<li>简单网络管理协议（SNMP）：管理进程和代理进程之间的通信协议</li>
</ol>
<div class="blog_h2"><span class="graybg">管理信息结构</span></div>
<p>SNMP中定义了以下数据类型（其中全大写的是来自ASN.1的内建类型）：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;">数据类型 </td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>INTEGER</td>
<td>整数类型。某些变量还具有额外的范围限制，例如UDP端口号必须在0-65535之间</td>
</tr>
<tr>
<td>OCTET STRING</td>
<td>0或者多个8bit的字节构成的字符串，每个字节的值在0-255之间</td>
</tr>
<tr>
<td>OBJECT IDENTIFIER</td>
<td>即对象标识符（OID），在一个全世界范围树状结构中注册</td>
</tr>
<tr>
<td>NULL</td>
<td>表示相关的变量没有值</td>
</tr>
<tr>
<td>SEQUENCE</td>
<td>序列，一个序列可以包含0-N个元素，每个元素可以具有不同的数据类型，类似与C语言的结构体</td>
</tr>
<tr>
<td>SEQUENCE OF</td>
<td>
<p>向量，其中所有元素具有相同的类型。</p>
<p>如果元素是简单类型，例如整数，那么就是一维向量。</p>
<p>如果元素是Sequence，那么可以看做二维数组或者表</p>
</td>
</tr>
<tr>
<td>DisplayString</td>
<td>即OCTET STRING</td>
</tr>
<tr>
<td>IpAddress</td>
<td>4字节的OcterString，以网络序表示的IP地址</td>
</tr>
<tr>
<td>PhysAddress</td>
<td>OCTET STRING，物理地址，以太网物理地址6字节长</td>
</tr>
<tr>
<td>Counter</td>
<td>0-2^32-1之间的整数，达到最大值后归0</td>
</tr>
<tr>
<td>Gauge</td>
<td>0-2^32-1之间的整数，达到最大值后锁定，直到复位</td>
</tr>
<tr>
<td>TimeTicks</td>
<td>时间计数器，以0.01秒为单位递增</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">OID</span></div>
<p>对象标识符（OID）用来识别一个“授权的”命名对象。所谓“授权”是指OID必须由权威机构进行管理和分配，而不是随意使用。</p>
<p>OID的形式是点号分割的整数序列，这些整数序列形成<span style="background-color: #c0c0c0;">树形结构</span>，类似与DNS或者UNIX文件系统的结构。另外，每一个整数还对应了一个文字的名字，这个名字是为了便于阅读的。OID树的结构示例如下图：</p>
<p><img class="aligncenter  wp-image-8279" src="https://blog.gmem.cc/wp-content/uploads/2012/05/internet-smi.png" alt="internet-smi" width="557" height="452" /></p>
<p>可以看到，所有SNMP MIB-2公共对象都定义在1.3.6.1.2.1下。而<span style="background-color: #c0c0c0;">厂商自定义</span>MIB对象都定义在<span style="background-color: #c0c0c0;">1.3.6.1.4.1</span>下。</p>
<div class="blog_h2"><span class="graybg">管理信息库</span></div>
<p>所谓管理信息库，就是所有代理进程包含的、并且能够被管理进程进行查询和设置的信息的集合。管理信息库整体上以OID树为基础进行组织，它定义了信息（树节点）的详细规格——例如OID、数据类型、描述等。</p>
<p>管理信息库是通过ASN.1语言来定义的，对应的文件一般称为MIB（定义）文件。有关MIB定义文件的细节，我们在后续章节进行说明。</p>
<p>这里以1.3.6.1.2.1.7即udp组（定义在RFC1213-MIB中）为例来阐述管理信息库的运行时结构，该组比较简单，包含4简单变量、以及一个由两个简单变量组成的表格：</p>
<p><img class="aligncenter  wp-image-8281" src="https://blog.gmem.cc/wp-content/uploads/2012/05/udp-smi.png" alt="udp-smi" width="533" height="254" /></p>
<p>udp组下的4个简单变量可以用如下格式来描述：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;"> 名称</td>
<td style="width: 100px; text-align: center;">数据类型</td>
<td style="width: 80px; text-align: center;">R/W</td>
<td style="text-align: center;">描述 </td>
</tr>
</thead>
<tbody>
<tr>
<td>udpInDatagrams</td>
<td>Counter</td>
<td> </td>
<td>UDP输入数据报个数</td>
</tr>
<tr>
<td>udpNoPort</td>
<td>Counter</td>
<td> </td>
<td>没有发送到有效端口的UDP数据报个数</td>
</tr>
<tr>
<td>udpInErrors</td>
<td>Counter</td>
<td> </td>
<td>接收到的有错误的UDP数据报个数</td>
</tr>
<tr>
<td>udpOutDatagrams</td>
<td>Counter</td>
<td> </td>
<td>UDP数据报输出数</td>
</tr>
</tbody>
</table>
<p>R/W表示是否可读写，为空表示只读。如果变量可写，则使用点号（.）标注。如果数据类型有范围限制，则用 [lower, upper]形式标注。</p>
<p>udpTable（UDP监听表）下定义包含的两个简单变量我们可以用如下格式描述：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;" colspan="4">UDP监听表，索引=&lt;udpLocalAddress&gt;.&lt;udpLocalPort&gt;</td>
</tr>
<tr>
<td style="width: 150px; text-align: center;"> 名称</td>
<td style="width: 100px; text-align: center;">数据类型</td>
<td style="width: 80px; text-align: center;">R/W</td>
<td style="text-align: center;">描述 </td>
</tr>
</thead>
<tbody>
<tr>
<td>udpLocalAddress</td>
<td>IpAddress</td>
<td> </td>
<td>监听的本地地址</td>
</tr>
<tr>
<td>udpLocalPort</td>
<td>[0..65535]</td>
<td> </td>
<td>监听的端口号</td>
</tr>
</tbody>
</table>
<p>注意我们在第一行描述了表的索引，即说明了列的先后顺序。</p>
<div class="blog_h3"><span class="graybg">实例标识</span></div>
<p>当对MIB变量进行读写时，必须对每个<span style="background-color: #c0c0c0;">MIB变量进行标识（而不是仅仅使用OID）</span>。需要注意的是，<span style="background-color: #c0c0c0;">只有叶子节点是可操作的</span>，SNMP没法操作表格的一整行或者一整列。</p>
<p><span style="text-decoration: underline;"><strong>简单变量</strong></span></p>
<p>对于简单变量，我们通过<span style="background-color: #c0c0c0;">在OID后面添加.0</span>来作为实例标识。例如udpInDatagrams的OID是1.3.6.1.2.1.7.1，那么它的实例标识则是1.3.6.1.2.1.7.1.0。这个实例标识会用在SNMP报文中。</p>
<p><strong><span style="text-decoration: underline;">表格</span></strong></p>
<p>对于表格，实例标识比较复杂。 假设监听表中包含三个条目：0.0.0.0:67、0.0.0.0:161、0.0.0.0:520。那么表格实例标识是如何组织的呢？</p>
<p>SNMP使用<span style="background-color: #c0c0c0;">OID.点号分隔的值列表</span>的形式来表示表格中每一个单元格的实例标识，对于表格的每一列，OID是一致的；对于表格的每一行，值列表是一致的。按照这一规则：</p>
<ol>
<li>条目0.0.0.0:67的udpLocalAddress的实例标识为：<span style="background-color: #ccffff;">1.3.6.1.2.1.7.5.1.1</span>.<span style="background-color: #ccffcc;">0.0.0.0</span>.<span style="background-color: #ccffcc;">67</span></li>
<li>条目0.0.0.0:67的udpLocalPort的实例标识为：        <span style="background-color: #ccffff;">1.3.6.1.2.1.7.5.1.2</span>.<span style="background-color: #ccffcc;">0.0.0.0</span>.<span style="background-color: #ccffcc;">67</span></li>
</ol>
<p>在SNMP代理内部，<span style="background-color: #c0c0c0;">对象标识（OID）是按字典式排序的</span>，并且对于表格，是按照<span style="background-color: #c0c0c0;">先列后行的顺序排列</span>，因此三个条目（6个变量）的排列顺序将如下：</p>
<p><img class="aligncenter  wp-image-8287" src="https://blog.gmem.cc/wp-content/uploads/2012/05/smi-udp-table.png" alt="smi-udp-table" width="271" height="111" /></p>
<p>get-next操作是基于上述排序的，我们可以使用snmpi命令来多次get-next操作，并观察它如何遍历UDP监听表格： </p>
<pre class="crayon-plain-tag">snmpi -a 127.0.0.1 -c ******
#udpTable不是叶子节点，不需要也不能指定实例，但是get-next操作仍然能返回表格中下一个对象
snmpi &gt; next udpTable    
#输出 udpLocalAddress.0.0.0.0.67=0.0.0.0  第1个条目，返回第1列第1行的值
snmpi &gt; next udpLocalAddress.0.0.0.0.67
#输出 udpLocalAddress.0.0.0.0.161=0.0.0.0 第2个条目，返回第1列第2行的值
snmpi &gt; next udpLocalAddress.0.0.0.0.161
#输出 udpLocalAddress.0.0.0.0.250=0.0.0.0 第3个条目，返回第1列第3行的值
snmpi &gt; next udpLocalAddress.0.0.0.0.250
#输出 udpLocalPort.0.0.0.0.67=67          第1个条目，返回第2列第1行的值
snmpi &gt; next udpLocalPort.0.0.0.0.67
#输出 udpLocalPort.0.0.0.0.161=161        第1个条目，返回第2列第2行的值
snmpi &gt; next udpLocalPort.0.0.0.0.161
#输出 udpLocalPort.0.0.0.0.520=520        第1个条目，返回第2列第3行的值
snmpi &gt; next udpLocalPort.0.0.0.0.520
#输出 snmpInPkts.0=59，变量名称前缀发生变化，提示遍历结束</pre>
<div class="blog_h2"><span class="graybg">ASN.1和BER</span></div>
<p>ASN.1（Abstract Syntax Notation 1）是一种<span style="background-color: #c0c0c0;">描述数据和数据特征的</span>正式语言，它和数据的存储及编码无关。在正式的SNMP规范中，MIB和SNMP报文中所有字段的规格都是用ASN.1来描述的。下面的代码示例了ASN.1抽象语法：</p>
<pre class="crayon-plain-tag">-- 类型定义语法：[新类型的名字] ::= [类型描述] 
-- 类型名字一般以大写字母开头
Married ::= BOOLEAN
StaffEntry ::= SEQUENCE 
{ 
    staffCode OCTET STRING,
    dob INTEGER
}
-- 值定义语法：[新的值的名字] [该值的类型] ::= [值描述]
system OBJECT IDENTIFIER ::= { mib-2 1 }
pair Coordinates ::= { x 5, y -3 }
counter Counter ::=0

-- 模块定义语法：
-- [模块名]  DEFINITIONS [缺省标记] ::=
-- BEGIN 
-- EXPORTS [导出描述]
-- IMPORTS [导入描述]
-- [模块体]
-- END 

RFC1213-MIB DEFINITIONS ::= BEGIN
    IMPORTS OBJECT-TYPE FROM RFC-1212;

    DisplayString ::= OCTET STRING
END</pre>
<p> 有了这样的一个抽象化定义，可以设计不同的编码方式把数据编码为比特流。SNMP使用的方法是BER（Basic Encoding Rule），BER是SNMP报文中比特的编码方式，对于10这个简单的整数，BER需要3个字节来表示：</p>
<ol>
<li>第一个字节说明类型是一个整数</li>
<li>第二个字节说明后面有多少字节用来存放这个数（这里是1）</li>
<li>第三个字节存放真正的数值</li>
</ol>
<p>ASN.1、BER是SNMP协议的实现者需要关心的内容。</p>
<div class="blog_h2"><span class="graybg">SNMP协议</span></div>
<div class="blog_h3"><span class="graybg">SNMP v1</span></div>
<p>最初版本的SNMP协议在1990年，由RFC 1157发布，它定义了5种报文，来表示管理进程与代理进程之间的交互信息（其中前三个由管理进程发出，后二个由代理进程发出）：</p>
<ol>
<li>get-request：从代理进程处提取一个或多个变量值</li>
<li>get-next-request：从代理进程处提取一个或多个变量的“下一个”变量值</li>
<li>set-request：设置代理进程的一个或多个变量值</li>
<li>get-response：返回的一个或多个变量值</li>
<li>trap：通知管理进程有某些事情发生</li>
</ol>
<p>这些报文的关系如下图所示：</p>
<p><img class="aligncenter  wp-image-5294" src="https://blog.gmem.cc/wp-content/uploads/2012/03/snmp-msg.png" alt="snmp-msg" width="509" height="273" /></p>
<p>这些报文的结构如下：</p>
<p><img class="aligncenter  wp-image-5295" src="https://blog.gmem.cc/wp-content/uploads/2012/03/snmp-msg-structure.png" alt="snmp-msg-structure" width="654" height="328" /></p>
<p>除了trap报文的结构描述如下：</p>
<ol>
<li>版本：SNMP版本号-1得到</li>
<li>共同体（团体名）：一个字符串。这是管理进程和代理进程之间的口令，明文格式。默认的值是public</li>
<li>PDU类型：即协议数据单元类型，如下表：<img class="aligncenter size-full wp-image-5297" src="https://blog.gmem.cc/wp-content/uploads/2012/03/pdu.png" alt="pdu" width="330" height="171" />在TCP/IP协议族中，表示某个协议的对等实体之间进行交换的单位信息（即单个报文）。SNMP定义了多种报文格式，因此它使用PDU类型来区分不同的报文</li>
<li>请求标识：对于get*、get-next*、set操作，请求标识由管理进程设置，然后由代理进程在get-response中返回。用于匹配查询与应答</li>
<li>差错状态：是由代理进程标注指明有差错发生，如下表：<img class="aligncenter  wp-image-5298" src="https://blog.gmem.cc/wp-content/uploads/2012/03/snmp-header-exception.png" alt="snmp-header-exception" width="529" height="139" /></li>
<li>差错索引：一个整数偏移量，指明当有差错发生时，差错发生在哪个变量。它是由代理进程标注的，并且只有在发生noSuchName、readOnly和badValue差错时才进行标注</li>
<li>对于get、get-next、set请求数据报，包含变量名称、变量值的一张表。其中get、get-next的变量值不用填写</li>
</ol>
<p>trap报文的结构描述如下：</p>
<ol>
<li>代理地址：即发送trap报文的代理进程的地址</li>
<li>trap类型：如下表：<img class="aligncenter  wp-image-5301" src="https://blog.gmem.cc/wp-content/uploads/2012/03/trap-structure.png" alt="trap-structure" width="596" height="214" /></li>
</ol>
<div class="blog_h3"><span class="graybg">SNMP v2</span></div>
<p>1993年新的SNMP协议被发布，它包含以下改进：</p>
<ol>
<li>定义了新的报文：get-bulk-request，可以高效率的从代理进程获取大量数据。可以用来代替迭代式的GetNextRequests</li>
<li>定义了新的报文：inform-request，使一个管理进程可以向另一个管理进程发送信息</li>
<li>定义了两个新的MIB：SNMP v2 MIB以及SNMP v2-M2M MIB</li>
<li>提高了安全性：SNMP v1中团体名是以明文方式发送的，现在可以提高身份验证和加密强度</li>
</ol>
<p>SNMP v2还包含一些变体：</p>
<ol>
<li>SNMPv2c：即基于团体名的SNMP v2（Community-Based Simple Network Management Protocol version 2）。它剔除了SNMP v2有争议的新安全模型，仍然使用SNMP v1的基于团体名的安全模型。该版本被认为是<span style="background-color: #c0c0c0;">实际上的SNMP v2标准</span></li>
<li>SNMPv2u：即基于用户的SNMP v2（User-Based Simple Network Management Protocol version 2）。它尝试在提高安全性的同时，避免引入SNMP v2的复杂性</li>
</ol>
<p>SNMP v2c和SNMP v1存在兼容性问题：</p>
<ol>
<li>SNMP v2c使用了不同格式的报文头，以及PDU格式。特别是，在SNMPv1中被作为特殊报文格式看待的trap，现在格式其它PDU例如get-request一样</li>
<li>引入了SNMP v1没有的新的协议报文</li>
</ol>
<div class="blog_h3"><span class="graybg">SNMP v3</span></div>
<p>这是SNMP协议的最新版本，通过对数据进行身份验证和加密，SNMP v3确保了防篡改、保密等安全特性。</p>
<p>除了密码学安全性的增强外，SNMP3还增加了新的PDU类型，例如report</p>
<div class="blog_h2"><span class="graybg">MIB定义文件</span></div>
<p>下面是MIB文件RFC1213-MIB中的片段，包含了本文用到的一些节点的定义：</p>
<pre class="crayon-plain-tag">--
-- 注释以两个小横线开头
--
    -- 整个MIB文件呈：[模块名] DEFINITIONS := BEGIN  [模块定义]  END的形式
	RFC1213-MIB DEFINITIONS ::= BEGIN
        -- 声明从其它模块导入的数据类型、宏和节点定义
		IMPORTS
			OBJECT-TYPE	  -- 这是一个宏
				FROM RFC-1212			
			mgmt, TimeTicks, IpAddress, Counter, Gauge,   -- 这是数据类型
			NetworkAddress   -- 这是一个节点
				FROM RFC1155-SMI;
	
	
--
-- 模块定义的类型
--
	
		DisplayString ::= OCTET STRING

		PhysAddress ::= OCTET STRING

	
--
-- 模块定义的节点
--
	    -- 这是一个简单的OID声明，类似于名字空间
		-- 1.3.6.1.2.1
		mib-2 OBJECT IDENTIFIER ::= { mgmt 1 }   -- 花括号中的部分属于类型描述，说明了父节点的名字，以及当前节点的编号

		
		-- 1.3.6.1.2.1.1
		system OBJECT IDENTIFIER ::= { mib-2 1 }

		
        -- 这是一个标量定义
		-- 1.3.6.1.2.1.1.1
		sysDescr OBJECT-TYPE   -- OBJECT-TYPE宏用于定义标量、表格或者表格条目 
            -- SYNTAX指明节点的数据类型，这里指明类型为DisplayString，尺寸限制为0-255个字符
			SYNTAX DisplayString (SIZE (0..255))
            -- 最大访问权限
			ACCESS read-only
            -- 节点状态
			STATUS mandatory
			DESCRIPTION
				"A textual description of the entity.  This value
				should include the full name and version
				identification of the system's hardware type,
				software operating-system, and networking
				software.  It is mandatory that this only contain
				printable ASCII characters."
			::= { system 1 }
         
        -- UDP监听表格的定义         
		-- 1.3.6.1.2.1.7.5
		udpTable OBJECT-TYPE
			SYNTAX SEQUENCE OF UdpEntry   --该表格是UdpEntry类型的序列
			ACCESS not-accessible
			STATUS mandatory
			DESCRIPTION
				"A table containing UDP listener information."
			::= { udp 5 }

		-- UDP监听表格条目定义
		-- 1.3.6.1.2.1.7.5.1
		udpEntry OBJECT-TYPE
			SYNTAX UdpEntry  -- 一个SEQUENCE类型
			ACCESS not-accessible
			STATUS mandatory
			DESCRIPTION
				"Information about a particular current UDP
				listener."
			INDEX { udpLocalAddress, udpLocalPort }  -- 索引，确定了列顺序，列顺序则确定了表格单元格实例OID的编码方式
			::= { udpTable 1 }

		-- 声明UdpEntry类型，这是一个包含2字段的序列
		UdpEntry ::=
			SEQUENCE { 
				udpLocalAddress
					IpAddress,
				udpLocalPort
					INTEGER
			 }

		-- 1.3.6.1.2.1.7.5.1.1
		udpLocalAddress OBJECT-TYPE
			SYNTAX IpAddress
			ACCESS read-only
			STATUS mandatory
			DESCRIPTION
				"The local IP address for this UDP listener.  In
				the case of a UDP listener which is willing to
				accept datagrams for any IP interface associated
				with the node, the value 0.0.0.0 is used."
			::= { udpEntry 1 }

		
		-- 1.3.6.1.2.1.7.5.1.2
		udpLocalPort OBJECT-TYPE
			SYNTAX INTEGER (0..65535)   -- 端口，具有数据范围限制
			ACCESS read-only
			STATUS mandatory
			DESCRIPTION
				"The local port number for this UDP listener."
			::= { udpEntry 2 }

		
	
	END  -- 这里表示模块定义结束</pre>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/snmp-study-note">SNMP协议学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/snmp-study-note/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
