使用 snmp4j 开发 SnmpTrap V3 版本服务端

发布时间 2023-04-17 14:18:35作者: BigBender

SnmpTrap 

snmp,是简单网络协议,snmpTrap是硬件设备遇到异常时主动推送给服务端的消息

安装好snmptrap依赖后,执行snmptrap -h,查看帮助

可以看到有3个版本,1、2c 和 3

其中1和2c使用的团体名,而3版本添加了新的认证机制

常规参数 -r 重试次数 和 -t 超时时间

指令格式

常用第二种格式

v1 和 v2c 版本

snmptrap -v 1 -c public 127.0.0.1:9162 "222" .0.1.2.3.4.5 1.3.6.1.4.1.2345 s "just here"
snmptrap -v 2c -c public 127.0.0.1:9162 "111" .0.1.2.3.4.5 1.3.6.1.4.1.2345 s "just here“
  snmptrap
  -v 1/2c

   -c public

  "222"
  .0.1.2.3.4.5
     1.3.6.1.4.1.2345 s "just here"
        指令头       版本         团体名      运行时间            trapOid                       oid           type      value

 

 

 

v3 版本,由于该版本使用了 User Security Model,参数较多

snmptrap -v 3 -u username -l authPriv -a MD5 -A mypassword -x AES -X mypassword 127.0.0.1:9162 "111" .0.1.2.3.4.5 1.3.6.1.4.1.2345 s "just here"
   snmptrap -v
 -v 3
 -u user
 -l authPriv
-a MD5
 -A mypassword
  -x AES   -X mypassword    "111"  .0.1.2.3.4.5
1.3.6.1.4.1.2345 s "just here"
            指令头   版本    安全用户名         安全等级    认证协议      认证协议的密码     隐私协议     隐私协议的密码   运行时间        trapOid              oid           type      value

安全等级 security level 有三个

  • noAuthNoPriv

  • authNoPriv

  • authPriv

也就是说参数 -a -A -x -X 都是可选的

Snmp4j的使用

GenericAddress,其中的 parse 方法是根据冒号分割字符串,得到对应的 Address ((char)58 就是 :)

 

Snmp

这个是snmp对象,可以添加tcp、udp、ssh监听服务,可以单线程也可配置线程池使用多线程

DefaultUdpTransportMapping

这个是服务端监听功能

  • udp 使用 java.net.DatagramSocket
  • tcp 使用 java.nio.channels.ServerSocketChannel

CommandResponder

这是个接口,实现之后可以直接获取设备推送过来的 Pdu 包

单线程不配置协议

private int port = 9162;

public void startup() {
    try {
        String address = String.format("udp:0.0.0.0/%d", port);
        UdpAddress udpAddress = (UdpAddress) GenericAddress.parse(address);
        snmp = new Snmp(new DefaultUdpTransportMapping(udpAddress));                    
        snmp.addCommandResponder(new SnmptrapResponder());
        System.out.println("Snmptrap receiver startup, listening : " + address); 
        snmp.listen();
    } catch (IOException e) {
        System.out.printf("Create snmpTrap receiver port[%d] failed%n", port);
    }
}
private class SnmptrapResponder implements CommandResponder {
    @Override
    public void processPdu(CommandResponderEvent event) {
        System.out.println(event.toString());
    }
}

多线程且配置协议

package com.example.raw_spring.server;

import org.snmp4j.*;
import org.snmp4j.mp.MPv1;
import org.snmp4j.mp.MPv2c;
import org.snmp4j.mp.MPv3;
import org.snmp4j.security.*;
import org.snmp4j.smi.GenericAddress;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.UdpAddress;
import org.snmp4j.transport.DefaultUdpTransportMapping;
import org.snmp4j.util.MultiThreadedMessageDispatcher;
import org.snmp4j.util.ThreadPool;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * Powered By liu_bin On 2022-11-30 14:21:56
 *
 * @author liu_bin
 * @version 1.0
 * @since 1.8
 */
@Component
public class SnmpTrapServerV3 implements CommandResponder, InitializingBean {

    @Value("${snmptrap.enable:true}")
    private boolean enable;
    @Value("${snmptrap.port:9162}")
    private int port = 9162;
    @Value("${snmptrap.v3.user:appuser,1qaz)OKM2wsx(IJN,1qaz)OKM2wsx(IJN}")
    private String user = "appuser,1qaz)OKM2wsx(IJN,1qaz)OKM2wsx(IJN";

    private Snmp snmp;
    private MultiThreadedMessageDispatcher dispatcher;
    private ThreadPool threadPool;

    private void init() throws Exception {

        // create thread pool for receiving snmp trap v3 message
        threadPool = ThreadPool.create("trap_v3", 4);
        dispatcher = new MultiThreadedMessageDispatcher(threadPool,
                new MessageDispatcherImpl());

        // create snmp server
        String strAddr = String.format("udp:0.0.0.0/%d", port);
        UdpAddress address = (UdpAddress) GenericAddress.parse(strAddr);
        snmp = new Snmp(dispatcher, new DefaultUdpTransportMapping(address));

        // set User-based SecurityModel
        USM usm = new USM(SecurityProtocols.getInstance(),
                new OctetString(MPv3.createLocalEngineID()), 0);

        // add security protocols
        SecurityProtocols.getInstance().addDefaultProtocols();
        SecurityProtocols.getInstance().addAuthenticationProtocol(new AuthSHA());
        SecurityProtocols.getInstance().addPrivacyProtocol(new PrivAES128());

        // create & convert usm user
        String[] rawUsers = user.split(";");
        String[] rawUser;

        for (String s : rawUsers) {
            rawUser = s.split(",");
            usm.addUser(new OctetString(rawUser[0]), new UsmUser(new OctetString(rawUser[0]), AuthSHA.ID, new OctetString(rawUser[1]), PrivAES128.ID, new OctetString(rawUser[2])));
        }

        snmp.getMessageDispatcher().addMessageProcessingModel(new MPv1());
        snmp.getMessageDispatcher().addMessageProcessingModel(new MPv2c());
        snmp.getMessageDispatcher().addMessageProcessingModel(new MPv3(usm));

        /**
         * new MPv3(usm) 这段
         * 通过观察MPv3的构造函数发现其中的
         * SecurityModels.getCollection(new SecurityModel[]{usm})
         * 等同于下面两句
         * SecurityModels.getInstance().addSecurityModel(usm);
         * snmp.getMessageDispatcher().addMessageProcessingModel(new MPv3());
         */

        // start listening...
        snmp.listen();
        System.out.println("SnmpTrap sever v3 start listening:" + strAddr);
    }

    @Override
    public void processPdu(CommandResponderEvent event) {
        String community = null;
        if (event.getPDU().getType() == PDU.V1TRAP || event.getPDU().getType() == PDU.TRAP)  {
            community = new String(event.getSecurityName());
            System.out.println("团体名: " + community);
        }
        System.out.println(event);
    }


    @Override
    public void afterPropertiesSet() throws Exception {
        if(enable) {
            try {
                init();
                snmp.addCommandResponder(this);
            } catch (Exception ex) {
                System.out.println("SnmpTrapSever v3 启动失败:" + ex.getMessage());
            }
        }
    }
}

注意 processPdu 方法里的 event.getSecurityName 在 v1 和 v2c 获取到的就是团体名