SNMP架构主要由三个关键部分组成:网络管理系统(Network Management System,NMS)、代理(Agent)以及管理信息库(Management Information Base,MIB)。NMS 在网络管理中充当管理者的角色,通常运行在服务器上,负责向各个设备上的Agent发送管理请求,以查询或修改设备的参数值。同时,NMS也接收来自Agent主动发送的Trap信息,从而及时了解被管理设备的状态变化。
Agent则是驻留在被管理设备中的代理进程,其主要职责是维护设备的信息数据,并对NMS发送的请求做出响应,将操作结果反馈给 NMS。当设备发生故障或其他重要事件时,Agent会主动向NMS发送Trap信息。MIB则是网络设备上被 SNMP 管理的参数集合,它采用层次化的结构,类似于文件系统的目录结构,每个管理对象在MIB中都有其唯一的位置标识,即对象标识符(Object Identifier,OID)。通过OID,NMS和Agent能够准确地定位和操作特定的管理对象。
SNMP基于请求/响应模型进行工作。NMS通过向Agent发送不同类型的请求报文来实现对设备的管理操作。常见的请求类型包括 Get 请求,用于从Agent获取一个或多个参数值;Set请求,用于修改 Agent 上的一个或多个参数值;GetNext请求,通常用于遍历 MIB 中的表结构,获取下一个对象的值;GetBulk请求,适用于一次性获取大量数据,特别是在检索表格信息时能显著提高效率;Inform请求,Agent使用该请求向NMS发送通知,与Trap类似,但Inform请求需要NMS进行确认。
Agent在接收到NMS的请求后,会根据请求类型在MIB中查找或修改相应的管理对象,并将结果封装在响应报文中返回给 NMS。当设备出现特定事件(如接口状态改变、设备故障等)时,Agent会主动向NMS发送Trap报文,通知NMS设备端发生的情况。
SNMPv3:在安全性方面做了显著改进,支持用户级别的认证和加密。它提供了三种主要的安全功能:认证,用于确保消息的发送者身份真实可靠;加密,保护数据在传输过程中的机密性,防止数据被窃取;消息完整性,保证消息在传输过程中未被篡改。通过这些安全特性,SNMPv3能够更好地满足现代网络对安全性的要求,适用于对安全较为敏感的网络环境。
<dependency> <groupId>org.snmp4j</groupId> <artifactId>snmp4j</artifactId> <version>2.8.4</version> </dependency>application.yml配置示例:
snmp: target: ip: 192.168.1.1 port: 161 version: v2c community: public timeout: 3000 retries: 2 trap: listener-port: 162配置类实现:
@Configuration @ConfigurationProperties(prefix = "snmp") public class SnmpConfig { private TargetConfig target; private String version; private String community; private int timeout; private int retries; private TrapConfig trap; // 堆代码 duidaima.com // 内部静态类用于封装目标设备配置 public static class TargetConfig { private String ip; private int port; // getters and setters } public static class TrapConfig { private int listenerPort; // getters and setters } // getters and setters }SNMP 客户端初始化
@Configuration public class SnmpClientConfig { @Autowired private SnmpConfig snmpConfig; @Bean public Snmp snmp() throws IOException { // 创建传输层对象(UDP协议) TransportMapping<?> transport = new DefaultUdpTransportMapping(); Snmp snmp = new Snmp(transport); transport.listen(); // 启动监听(用于接收响应) return snmp; } @Bean public Target snmpTarget() { Address targetAddress = GenericAddress.parse( "udp:" + snmpConfig.getTarget().getIp() + "/" + snmpConfig.getTarget().getPort() ); CommunityTarget target = new CommunityTarget(); target.setCommunity(new OctetString(snmpConfig.getCommunity())); target.setAddress(targetAddress); // 设置SNMP版本 if ("v3".equals(snmpConfig.getVersion())) { target.setVersion(SnmpConstants.version3); } elseif ("v2c".equals(snmpConfig.getVersion())) { target.setVersion(SnmpConstants.version2c); } else { target.setVersion(SnmpConstants.version1); } target.setTimeout(snmpConfig.getTimeout()); // 超时时间(毫秒) target.setRetries(snmpConfig.getRetries()); // 重试次数 return target; } }Get 请求实现
@Service public class SnmpService { @Autowired private Snmp snmp; @Autowired private Target target; /** * 发送Get请求获取单个OID的值 * @param oid 目标对象标识符 * @return 解析后的结果值 */ public String get(String oid) throws IOException { // 创建Get请求PDU PDU pdu = new PDU(); pdu.add(new VariableBinding(new OID(oid))); pdu.setType(PDU.GET); // 发送请求并获取响应 ResponseEvent event = snmp.send(pdu, target); PDU response = event.getResponse(); if (response == null) { throw new RuntimeException("未收到SNMP响应"); } elseif (response.getErrorStatus() != PDU.noError) { throw new RuntimeException( "SNMP错误: " + response.getErrorStatusText() + " (错误状态码: " + response.getErrorStatus() + ")" ); } // 解析响应结果 VariableBinding binding = response.get(0); return binding.getVariable().toString(); } /** * 批量获取多个OID的值 * @param oids OID列表 * @return 以Map形式返回OID与对应值的映射 */ public Map<String, String> getBulk(List<String> oids) throws IOException { PDU pdu = new PDU(); for (String oid : oids) { pdu.add(new VariableBinding(new OID(oid))); } pdu.setType(PDU.GETBULK); pdu.setMaxRepetitions(5); // 设置批量获取的最大重复次数 ResponseEvent event = snmp.send(pdu, target); PDU response = event.getResponse(); // 响应处理逻辑(省略,类似单个Get请求) Map<String, String> result = new HashMap<>(); for (VariableBinding binding : response.getVariableBindings()) { result.put(binding.getOid().toString(), binding.getVariable().toString()); } return result; } }Set 请求实现
/** * 发送Set请求修改设备参数 * @param oid 目标OID * @param value 要设置的新值 * @return 是否设置成功 */ public boolean set(String oid, String value) throws IOException { PDU pdu = new PDU(); Variable variable = new OctetString(value); pdu.add(new VariableBinding(new OID(oid), variable)); pdu.setType(PDU.SET); ResponseEvent event = snmp.send(pdu, target); PDU response = event.getResponse(); return response != null && response.getErrorStatus() == PDU.noError; }Trap 监听与处理
@Slf4j @Component public class SnmpTrapReceiver implements ApplicationRunner, CommandResponder { @Override public void processPdu(CommandResponderEvent commandResponderEvent) { try{ Map<String,Object> requestMap = new HashMap<>(); if (commandResponderEvent.getPDU().getType() == PDU.TRAP || commandResponderEvent.getPDU().getType() == PDU.V1TRAP) { PDU pdu=commandResponderEvent.getPDU(); if (pdu != null) { Vector<? extends VariableBinding> resVBs = pdu.getVariableBindings(); for (int i = 0; i < resVBs.size(); i++) { VariableBinding recVB = resVBs.elementAt(i); String oid = recVB.getOid().toString(); Variable variable = recVB.getVariable(); String valueStr = ""; if(variable instanceof OctetString){ OctetString octetString = (OctetString) variable; valueStr = StrUtil.utf8Str(octetString.getValue()); }elseif (variable instanceof Gauge32) { Gauge32 gauge32 = (Gauge32) variable; valueStr = String.valueOf(gauge32.getValue()); }elseif (variable instanceof Integer32) { Integer32 integer32 = (Integer32) variable; valueStr = String.valueOf(integer32.getValue()); }else { valueStr = variable.toString(); } log.info("oid:" + oid + " value:" + valueStr); } } } }catch (Exception e){ log.error("处理Trap信息异常,异常信息为:{}", e.getMessage()); } } @Override public void run(ApplicationArguments args){ try { ThreadPool threadPool = ThreadPool.create("snmptrap", 10); MultiThreadedMessageDispatcher dispatcher = new MultiThreadedMessageDispatcher(threadPool, new MessageDispatcherImpl()); Address listenAddress = GenericAddress.parse(System.getProperty("snmp4j.listenAddress", "udp:192.168.1.1/162")); TransportMapping transport; // 对TCP与UDP协议进行处理 if (listenAddress instanceof UdpAddress) { transport = new DefaultUdpTransportMapping((UdpAddress) listenAddress); log.info("使用UDP协议"); } else { transport = new DefaultTcpTransportMapping((TcpAddress) listenAddress); log.info("使用TCP协议"); } Snmp snmp = new Snmp(dispatcher, transport); snmp.getMessageDispatcher().addMessageProcessingModel(new MPv1()); snmp.getMessageDispatcher().addMessageProcessingModel(new MPv2c()); snmp.getMessageDispatcher().addMessageProcessingModel(new MPv3()); USM usm = new USM(SecurityProtocols.getInstance(), new OctetString(MPv3.createLocalEngineID()), 0); SecurityModels.getInstance().addSecurityModel(usm); snmp.listen(); snmp.addCommandResponder(this); log.info("开始监听Trap信息"); } catch (IOException e) { log.info("监听Trap信息异常,异常信息为:{}", e.getMessage()); } } }安全配置(SNMPv3)
public Target createV3Target() { UserTarget target = new UserTarget(); target.setAddress(GenericAddress.parse("udp:192.168.1.1/161")); target.setVersion(SnmpConstants.version3); target.setSecurityLevel(SecurityLevel.AUTH_PRIV); // 认证并加密 target.setSecurityName(new OctetString("admin")); // 用户名 return target; } // 初始化SNMPv3用户 @Bean public void initV3User() throws IOException { USM usm = new USM(SecurityProtocols.getInstance(), new OctetString(MPv3.createLocalEngineID()), 0); SecurityModels.getInstance().addSecurityModel(usm); // 添加用户(认证密码、加密密码) snmp.getUSM().addUser( new OctetString("admin"), new UsmUser( new OctetString("admin"), AuthMD5.ID, new OctetString("authPass123"), PrivDES.ID, new OctetString("privPass123") ) ); }