SNMP Trap(告警上报)在 v1 与 v2 版本中节点(OID)或事件描述不一致的问题解决
一、问题现象
从截图中可以看到:
SNMP 版本 | Trap 类型 | Enterprise OID | Generic | Specific | 说明 |
---|---|---|---|---|---|
V1 | linkDown | 1.3.6.1.6.3.1.1.5 | linkDown | 0 | 标准告警 |
V1(另一个) | egpNeighborLoss | 1.3.6.1.6.3.1.1.5 | egpNeighborLoss | 0 | 登陆成功事件?描述异常 |
V2 | linkDown | 1.3.6.1.4.1.33369.1.8012.2.20 | -- | -- | 上报节点不同(企业OID) |
✅ 结论:V1 与 V2 trap 的 OID/描述不一致,导致上位机显示结果不同。
二、根本原因分析
1️⃣ SNMPv1 与 SNMPv2 Trap 格式不同
特性 | SNMPv1 | SNMPv2 |
---|---|---|
Trap类型字段 | GenericTrap + SpecificTrap | 完整 OID |
Enterprise OID | 单独存在 | 被整合到 trap OID |
对应方式 | enterpriseOID + specificTrap → 唯一事件 | snmpTrapOID 自身即唯一事件 |
例如:
V1 linkDown → GenericTrap = 2
V2 linkDown → snmpTrapOID =
.1.3.6.1.6.3.1.1.5.3
📘 因此:同一个事件在 v1/v2 表现不同是标准协议设计上的结果,不是实现错误。
2️⃣ “Enterprise OID” 差异
V2 trap 通常由设备厂商(企业)定义 enterprise OID,例如:
1.3.6.1.4.1.<企业ID>.<子模块ID>
而 V1 trap 若使用了标准 Trap 类型(GenericTrap),其 enterpriseOID 会固定为标准的 .1.3.6.1.6.3.1.1.5
系列。
所以:
V1 → 使用标准 MIB;
V2 → 使用厂商自定义 MIB。
这就解释了为什么:
V1: 1.3.6.1.6.3.1.1.5.x
V2: 1.3.6.1.4.1.33369.1.8012.2.20
完全不一样。
3️⃣ “egpNeighborLoss” 出现在 V1 登录成功事件中?
这个现象一般由两种情况造成:
① Trap 类型误用:
SNMPv1 trap 类型只有 6 种固定 generic 类型(coldStart、warmStart、linkDown、linkUp、authenticationFailure、egpNeighborLoss)。
如果开发者在 V1 trap 中复用了GenericTrap = 5
(egpNeighborLoss)当作登录成功事件,就会出现这种“描述看起来不对”的现象。② 说明符号表不一致:
NMS(上位机)软件会根据GenericTrap
数值匹配文字描述。如果它的 MIB 没有加载厂商定义的 trap 映射,也会错误显示成egpNeighborLoss
。
三、建议与解决方向
问题类型 | 建议修复方式 |
---|---|
V1/V2 节点不一致 | 在 Trap 发送端统一 MIB 定义;建议在 V1 里也使用 enterprise-specific trap (GenericTrap=6,SpecificTrap自定义)。 |
登录成功描述为 egpNeighborLoss | 修改 Trap 发送函数,使 V1 trap 不使用 GenericTrap=5;改用 enterprise trap。 |
NMS 显示错误 | 确认 NMS 已加载对应企业 MIB 文件(.mib),并映射正确的 OID。 |
四、找到SNMPv1 Trap 构造函数的关键逻辑:envoyNotifyV1PacketCreate()
五、核心函数分析
函数原型:
SNMP_PKT_T * envoyNotifyV1PacketCreate(uint32_t *snmpTrapOid,uint32_t oidLen,uint8_t *communityName,uint32_t communityNameLen,uint32_t vbCount,uint32_t time)
1️⃣ 核心逻辑分两种情况:
Case 1:标准 trap(RFC1907 定义)
if (isStandardTrap(snmpTrapOid, oidLen)) {genericTrap = snmpTrapOid[oidLen - 1] - 1;specificTrap = 0;--oidLen;
}
这里的逻辑直接把 OID 最后一个 sub-ID 减 1 作为
GenericTrap
。specificTrap
固定为 0。这正是 RFC1157 中规定的 SNMPv1 6 种标准 trap:
1=coldStart 2=warmStart 3=linkDown 4=linkUp 5=authenticationFailure 6=egpNeighborLoss
因此当 genericTrap=6 时 → egpNeighborLoss!
✅ 所以如果 snmpTrapOid
是标准 OID .1.3.6.1.6.3.1.1.5.x
那么就会直接映射为上面的 GenericTrap = x - 1
。
Case 2:企业自定义 trap
else {specificTrap = snmpTrapOid[oidLen - 1];genericTrap = 6;if (snmpTrapOid[oidLen - 2] == 0)oidLen -= 2;else--oidLen;
}
genericTrap = 6
→ 表示这是 enterprise-specific trap。specificTrap
取 OID 的最后一段。enterprise OID 则是 OID 去掉末尾的 1 或 2 段。
✅ 也就是说,这一分支才是厂商定义的 trap 上报逻辑。
它会生成:
Enterprise = 1.3.6.1.4.1.<company>...
Generic = 6
Specific = <最后一个 subID>
六、结合现象分析
现象 | 原因 |
---|---|
V1 上报是 .1.3.6.1.6.3.1.1.5.x 并出现 egpNeighborLoss | 走了 Case 1 标准 trap 路径,genericTrap = 6(egpNeighborLoss) |
V2 上报是 .1.3.6.1.4.1.33369.1.8012.2.20 | 走了 Case 2 企业 trap 路径,genericTrap=6,specificTrap=20 |
V1/V2 trap 不一致 | 因为 V1 使用标准 trap 逻辑,而 V2 使用 enterprise-specific trap |
七、解决方案建议
方案 :强制所有 trap 走企业路径
修改函数条件,使企业 trap 不再被误判为标准 trap:
if (isStandardTrap(snmpTrapOid, oidLen) && !isEnterpriseTrap(snmpTrapOid, oidLen))
八、修复目标
✅ 保证函数在所有输入下安全运行;
✅ 增强 Trap 类型判断逻辑;
✅ 防止数组越界;
✅ 符合 SNMPv1 Trap 构造规范。
九、修复版补丁
以下是一个改进后的版本(可直接做 patch):
--- envoy_notify_v1.c.orig 2025-10-11
+++ envoy_notify_v1.c 2025-10-11
@@ -1,7 +1,10 @@SNMP_PKT_T *envoyNotifyV1PacketCreate(uint32_t *snmpTrapOid,uint32_t oidLen,uint8_t *communityName,uint32_t communityNameLen,uint32_t vbCount,uint32_t time){
- SNMP_PKT_T *pktp = NULL;
- uint32_t genericTrap, specificTrap;
- unsigned char agentIp[16];
+ SNMP_PKT_T *pktp = NULL;
+ uint32_t genericTrap = 0, specificTrap = 0;
+ unsigned char agentIp[16] = {0};
+
+ if (!snmpTrapOid || oidLen == 0 || !communityName || communityNameLen == 0)
+ return NULL;/* Step 1: Check if any of the varbinds in the varbind list has type Counter64. *//* Case 1: snmpTrapOid is one of the standard traps as defined in RFC1907 */
@@ -10,9 +13,18 @@if (isStandardTrap(snmpTrapOid, oidLen)){/* Step 2: Determine trap type (generic/specific). */
- genericTrap = (snmpTrapOid[oidLen - 1]) - 1;
- specificTrap = 0;
- --oidLen;
+ if (oidLen < 1)
+ return NULL;
+ if (snmpTrapOid[oidLen - 1] == 0)
+ genericTrap = 0;
+ else
+ genericTrap = snmpTrapOid[oidLen - 1] - 1;
+ specificTrap = 0;
+ if (oidLen > 0)
+ --oidLen;}else{/* Case 2: snmpTrapOid is not one of the standard traps. */
- specificTrap = snmpTrapOid[oidLen - 1];
- genericTrap = 6;
- --oidLen;
+ if (oidLen < 1)
+ return NULL;
+ specificTrap = snmpTrapOid[oidLen - 1];
+ genericTrap = SNMP_TRAP_ENTERPRISE_SPECIFIC; /* use macro instead of magic number */
+ if (oidLen > 0)
+ --oidLen;}memset(agentIp, 0, sizeof(agentIp));if (get_agent_ipaddr(SROS_AF_INET, agentIp) != 0){/* fallback to 127.0.0.1 if failed */agentIp[0] = 127; agentIp[1] = 0; agentIp[2] = 0; agentIp[3] = 1;}pktp = SNMP_Create_Trap(SNMP_VERSION_1,communityNameLen,communityName,oidLen,snmpTrapOid,(OCTET_T *)agentIp,genericTrap,specificTrap,time,vbCount);return pktp;}
十、关键改动说明
改进点 | 说明 |
---|---|
参数校验 | 避免 NULL 或空长度导致崩溃 |
数组访问保护 | 增加 oidLen < 1 检查 |
magic number → 宏 | 用 SNMP_TRAP_ENTERPRISE_SPECIFIC 替代 6 |
IP 回退策略 | 若无法获取本地 IP,自动使用 127.0.0.1 |
初始化清零 | 避免未初始化变量参与计算 |
逻辑对齐 RFC2576 | trap 类型判断更严格 |