snmp:基于 SNMP 的 iBMC 接口项目

Provide openUBMC SNMP interfaces enabling system information query and configuration operations

分支2Tags0

snmp


1. 组件概述

1.1 组件简介

snmp(简单网络管理协议)提供了 openUBMC 对外的访问接口之一,以 SNMP 协议实现。SNMP 接口包括 SNMP 查询和 SNMP设置。SNMP查询接口用于查询openUBMC系统的信息,SNMP设置接口用于设置、操作openUBMC系统。

1.2 解决了什么问题

简单网络管理协议 SNMP(Simple Network Management Protocol)用于网络设备的管理。网络设备多种多样,不同设备尝厂商提供的设备管理接口(如命令行接口)各不相同,这使得网络管理变得愈发复杂。为了解决这一问题,SNMP 应运而生。SNMP 作为广泛应用与TCP/IP网络的网络标准管理协议,提供了统一的接口,从而实现了不同种类和厂商的网络设备。openUBMC 的 snmp 组件由 net-snmp 的 snmpd 进程加载运行。组件本身不提供 D-Bus 服务、不直接处理 IPMI 命令,而是作为 SNMP 协议与资源协作接口之间的「协议代理 / 适配层」:将标准 SNMP Get/Set 请求映射到资源协作接口对象读写,新增 SNMP 可管理对象时无需修改 C 代码,只需新增一份映射配置。

1.3 核心功能:

  • 接口注册:依据映射配置把每条 SNMP 接口注册为 net-snmp 的 MIB 对象(标量或表),支持 Readwrite / Readonly / Setonly 三种访问模式。
  • Get/Set 处理:接收 net-snmp 请求,经路由映射(route_mapper)转换为资源协作接口属性读写。
  • 类型转换:在 ASN.1 类型(INTEGER、OCTET STRING、IpAddress、OBJECT IDENTIFIER)与 JSON 之间双向转换。
  • 表接口缓存:对 table 类型 GET 做 1s 短缓存,降低资源协作接口重复遍历开销。
  • 异步任务关联:支持 TaskReferenceUri,将 SNMP 请求关联到资源协作接口异步任务对象。
  • 访问来源记录:记录到达 BMC 的 SNMP 报文(来源 IP/端口、OID),用于审计与定界。
  • 可定制 OID 前缀:支持通过 SnmpOemIdentifier 替换 OEM OID 前缀。

1.4 与外部系统的交互边界:

SNMP 系统由网络管理系统 NMS(Network Management System)、SNMP Agent 、被管理对象 Management object 和管理信息库 MIB(Management Information Base)四部分组成。NMS 作为整个网络的网管中心,对设备进行管理。每隔被管理的设备中都包含驻留在设备上的 SNMP Agent 进程、MIB 和多个被管对象。NMS 提供与运行在被管理设备上的 SNMP Agent 交互,由 SNMP Agent 通过对设备端的MIB进行操作,完成 NMS 的指令。 SNMP 系统组成如下图所示:

flowchart LR
    subgraph Probe[Device]
        B(SNMP Agent) <--> C(MIB)
        C(MIB) <--> D(Managed object)
    end
    A(NMS) <--> B(SNMP Agent)

2. API 使用说明和示例

组件对外 API 分两层:Lua 全局 API(src/lualib/main.lua 导出,由 C 层调用)与 C 层入口函数(编译进 libsnmp.so)。

2.1 match

功能说明

接收一条 SNMP 请求,经路由映射转换为资源协作接口访问并返回结果,是 C 层 handler 与 Lua 层的桥接点。

参数说明

参数 类型 描述 必选
uri string 接口 URI,形如 /snmp/<oid>/<接口名>/<模式> 是
method string GET / PATCH / POST 是
address string 客户端 IP 是
username string SNMP 用户名(v1/v2c 为 SNMPv1_v2c) 是
reqbody string 请求体 JSON,GET 可为 nil 否

返回值与异常

固定 3 个值 err_code, ret_type, rsp_body。err_code 为 0(SNMP_ERR_NOERROR)/5(SNMP_ERR_GENERR)或错误消息中的 SnmpStatusCode;ret_type 为 "number"|"string"|"table"|"userdata";rsp_body 为响应值或错误描述字符串。

异常与触发条件:函数内部用 pcall 包裹,不向调用方抛异常,以 err_code + 描述返回。非 0 的典型触发:映射配置缺失、映射匹配抛异常、映射返回非空错误列表、响应体出现 null、响应体 JSON 编码失败。

2.2 其余 Lua API

API 作用 返回 异常
get_all_interface_uri_config() 返回所有 SNMP 接口的 URI/Name/Oid/Mode(启动注册用) JSON 字符串数组 不满足校验的 URI 被跳过并打印错误日志
get_sequence_elements_count(uri) 返回接口 Sequence 元素个数,区分标量/表 整数(标量 0) 无(非法返回 0)
get_interface_reqbody_config(uri) 返回 Set 请求体配置 JSON {Type, ReqBody:[{Name,Type}]} 空配置返回 ""
get_interface_primary_key(uri) 返回表接口主键名称与类型 JSON 数组 [{Name,Type}] 无(无 Sequence 返回 [])
get_interface_instance(uri) 返回表接口实例信息(带 1s 缓存) JSON {Count, PK, Index} 失败返回 ""
get_interface_sequence(uri) 返回表接口 Sequence 列描述 JSON 数组 无 Sequence 返回 ""

类型约束:ReqBody 属性类型仅支持 integer/string;Sequence 列 Type 支持 integer/string/IpAddress/ObjectIdentifier;主键类型仅 integer/string。

C 层入口函数:init_snmp_proxy()(初始化:加载 Lua 库/主脚本、注册接口)、register_interface()(注册标量/表接口)、interface_handle(...)(net-snmp handler 回调,分发 Get/Set 并记录访问来源)。

2.3 调试示例

本组件是 library,运行载体为 snmpd,调试入口为 net-snmp 客户端工具(snmpget/snmpwalk/snmpset/snmptable),非 busctl/mdbctl。

# 遍历本组件注册的 SNMP 接口(OEM OID 子树)
snmpwalk -v2c -c <community> <bmc_ip> 1.3.6.1.4.1.2011.2.235.1.1

# 读取标量接口(OID 后追加 .0;示例 trapEnable,见 test/unit/data/mapping_config/config.lua)
snmpget -v2c -c <community> <bmc_ip> 1.3.6.1.4.1.2011.2.235.1.1.4.1.0
# 示意响应(需环境验证):iso.3.6.1.4.1.2011.2.235.1.1.4.1.0 = INTEGER: 1

# 设置标量接口
snmpset -v2c -c <community> <bmc_ip> 1.3.6.1.4.1.2011.2.235.1.1.4.1.0 i 2

# 遍历表接口(示例 trapInfoDescriptionTable)
snmptable -v2c -c <community> <bmc_ip> 1.3.6.1.4.1.2011.2.235.1.1.4.50

SNMPv3 需追加 -v3 -l authPriv -u <user> -a <authproto> -A <authpass> -x <privproto> -X <privpass>。


3. 组件扩展案例

3.1 扩展能力概述

核心扩展能力是基于映射配置新增/定制 SNMP 接口,无需改代码。映射配置为 JSON 格式,源文件随产品仓提供,参考 rackmount 仓 rackmount/interface_config/snmp/(config.json 为全局变量配置,mapping_config/ 为接口映射配置,plugins/、script/、mib/ 分别为编排插件、校验脚本与 MIB 文件)。

3.2 扩展点:

(1)接口映射配置目录为 /opt/bmc/apps/snmp/interface_config/<产品目录>/mapping_config/(未区分产品时即 /opt/bmc/apps/snmp/interface_config/mapping_config/),其下按功能模块存放 .json 映射文件。

(2)加载映射配置时,组件从 innerbus 读取 platform_id、board_id 两个寄存器值,拼接为目录名 <platform_id>_<board_id>(十六进制 %02x_%02x);若目录 /opt/bmc/apps/snmp/interface_config/<platform_id>_<board_id> 存在,则使用该路径,否则使用默认路径 /opt/bmc/apps/snmp/interface_config。

3.3 二次开发指导:

config.json 定义 OEM OID 变量,供映射配置中的 {{SnmpOemIdentifier}} 占位符引用:

{
    "GlobalVariable": {
        "SnmpOemIdentifier": "2011.2.235.1.1"
    }
}

各模块 .json 含 Resources 数组,每条资源描述一个 SNMP 接口,Uri 中经 {{SnmpOemIdentifier}} 引用上述变量;Interfaces 描述该接口的 Get/PATCH/POST 行为(RspBody/ReqBody/Statements/ProcessingFlow):

示例

(标量接口 trapEnable,节选自 rackmount/interface_config/snmp/mapping_config/trap/trap.json):

{
    "Resources": [
        {
            "Uri": "/snmp/1.3.6.1.4.1.{{SnmpOemIdentifier}}.4.1/trapEnable/Readwrite",
            "Interfaces": [
                {
                    "Type": "Get",
                    "RspBody": { "Enabled": "${Statements/Enabled()}" },
                    "Statements": {
                        "Enabled": {
                            "Input": "${ProcessingFlow[1]/Destination/Enabled}",
                            "Steps": [ { "Type": "Switch", "Formula": [ { "Case": false, "To": 1 }, { "Case": true, "To": 2 } ] } ]
                        }
                    },
                    "ProcessingFlow": [
                        { "Type": "Property", "Path": "/bmc/kepler/EventService/Subscriptions/Snmp",
                          "Interface": "bmc.kepler.EventService.Subscriptions.Snmp",
                          "Destination": { "Enabled": "Enabled" } }
                    ]
                },
                {
                    "Type": "PATCH",
                    "ReqBody": { "Type": "object", "Required": true, "Properties": { "Enabled": { "Required": true, "Type": "integer", "Validator": [ { "Type": "Enum", "Formula": [ 1, 2 ] } ] } } },
                    "Statements": { "Enabled": { "Input": "${ReqBody/Enabled}", "Steps": [ { "Type": "Switch", "Formula": [ { "Case": 1, "To": false }, { "Case": 2, "To": true } ] } ] } },
                    "ProcessingFlow": [
                        { "Type": "Property", "Path": "/bmc/kepler/EventService/Subscriptions/Snmp",
                          "Interface": "bmc.kepler.EventService.Subscriptions.Snmp",
                          "Source": { "Enabled": "${Statements/Enabled()}" } }
                    ]
                }
            ]
        }
    ]
}

URI 格式与约束:/snmp/<oid>/<接口名>/<模式>。

  • <oid> 前缀须为通用前缀 1.3.6.1.2.1.1. 或 OEM 前缀 1.3.6.1.4.1.{{SnmpOemIdentifier}}.(默认 SnmpOemIdentifier = 2011.2.235.1.1,即 1.3.6.1.4.1.2011.2.235.1.1.);
  • URI 按 / 拆分须为 5 段;接口名非空;
  • <模式> 为 Readwrite/Readonly/Setonly;
  • 表接口需配置 Sequence 数组,元素含 Name/Type/Access(Readonly/Readwrite/Setonly)/Primary(主键标记),完整示例见 rackmount/interface_config/snmp/mapping_config/trap/trap.json 中的 trapInfoDescriptionTable。

验证方法:

将 JSON 配置放入映射目录并重启 snmpd → 用 snmpwalk/snmpget/snmpset 验证(见 2.3);单元测试 test/unit(test_interface.lua)使用 Lua 形式配置样例校验 URI 解析、请求体类型、主键数量、Sequence 类型等约束。

注意事项:

  • 表接口必须配主键(Primary=true,数量不为 0);简单接口 ReqBody 属性个数应为 1。
  • URI 不满足校验规则会被跳过(打印错误日志)。
  • 列 Access 为 Readonly 时,Set 会被拒(SNMP_ERR_NOACCESS)。
  • 产品专属配置需保证读取到 platform/board 信息且对应目录存在。

4. 日志说明

组件统一使用日志模块名 snmp(Lua 层 log:set_log_module_name("snmp"),C 层 set_log_module_name("snmp")),经统一日志框架输出。

关键日志信息:

日志 含义
initialize the Proxy of SNMP interface successfully.(INFO) 组件初始化成功
load the lua lib failed. / load the main start script failed.(ERROR) Lua 库/主脚本加载失败
register the SNMP interface failed.(ERROR) 接口注册失败
create the mapper of SNMP interface failed, error is %s.(ERROR) 创建路由映射器失败
The route configuration is missing.(ERROR) 路由配置缺失
the prefix of Uri(%s) should be ... / the Uri(%s) is invalid... / the prefix of Oid... / the interface name... / the mode of Uri...(ERROR) URI/OID/接口名/模式校验失败,接口被跳过
get the instance of interface(%s) failed, error is %s(ERROR) 表接口实例获取失败
the response body is nil / encode the response body failed, error is %s(ERROR) 响应体为空/编码失败
the response body of task is null.(NOTICE) 任务接口响应体为空

访问来源记录(net stream):每条到达 BMC 的 SNMP 报文经 syslog(LOG_LOCAL7 | LOG_ERR, ...) 记录一条来源日志(handler.c):

Received SNMP packet(s) from UDP: [<客户端IP>]:<端口>. oid:<OID>

未解析字段以 N/A 填充,是「一键日志收集」中定位 SNMP 访问来源的关键信息。


5. 问题定界指南

5.1 典型问题与定界:

现象 归属判断 关键日志/依据
snmpwalk/snmpget 返回 genErr(5) snmp 组件或资源协作接口 The route configuration is missing、the response body is nil、编码失败日志
snmpget 返回 noSuchObject/无该 OID snmp 组件(接口未注册) URI/OID/接口名/模式校验失败日志
snmpset 返回 noAccess(6) snmp 组件 接口 Mode 为 Readonly,或列 Access 为 Readonly
snmpset 返回 wrongType(7)/badValue(3) snmp 组件 请求类型与配置 Type 不匹配、转换失败
表接口 snmpwalk 无数据 snmp 组件或资源协作接口 the count of sequence is invalid、get the instance ... failed
SNMP 访问被拒(用户锁定) snmp 组件(user_login_lock.c)或账号服务 [user login lock] 日志
组件加载失败 snmp 组件 load the lua lib failed 等初始化日志

5.2 错误码

net-snmp 标准错误状态码,message.lua 与 C 层使用

错误码 名称 含义
0 SNMP_ERR_NOERROR 成功
3 SNMP_ERR_BADVALUE 值非法(类型/值转换失败)
5 SNMP_ERR_GENERR 一般性错误(映射失败、响应体 nil/编码失败)
6 SNMP_ERR_NOACCESS 无访问权限(只读属性被 Set)
7 SNMP_ERR_WRONGTYPE 类型错误(请求数据类型与配置不符)

5.3 调试/复现方法:

用 snmpwalk/snmpget/snmpset 复现 → 查看模块名 snmp 的日志,判断是「注册阶段跳过」还是「运行阶段失败」→ 核查映射配置校验规则、接口 Mode/列 Access、表接口主键配置、资源协作接口对象状态。


6. 常见问题解答

Q1:snmpget 访问某 OID 返回 genErr(5),如何定位?

  • **一句话答案:**先看组件日志(模块名 snmp)有无「映射失败/响应体 nil/编码失败」类错误。
  • 根因:genErr(5) 是映射失败、响应体为 null、响应体编码失败等情况统一返回的错误码。
  • **解决方案:**核查映射配置与资源协作接口对象,定位日志中 create the mapper ... failed、The route configuration is missing、the response body is nil 等关键信息。

Q2:新增 SNMP 接口需要改代码吗?

  • **一句话答案:**不需要,在映射配置目录新增 URI 配置即可。
  • **根因:**组件经 route_mapper 读取 interface_config/<product>/mapping_config 动态注册。
  • **解决方案:**按 3 示例配置,注意 URI 校验规则,重启 snmpd 后用 snmpwalk/snmpget 验证。

Q3:表接口 snmpwalk 遍历不到数据?

  • **一句话答案:**通常是 Sequence 主键配置缺失/非法,或资源协作接口对象无数据。
  • **根因:**表遍历依赖 Primary 主键构建索引;实例获取失败或 Count 为 0 导致无数据。
  • **解决方案:**检查 the count of sequence is invalid、get the instance ... failed 日志,核对主键配置与资源协作接口对象。

Q4:snmpset 被拒(noAccess/wrongType/badValue)?

  • **一句话答案:**分别对应「只读属性被写」「类型不符」「值非法」。
  • 根因:noAccess(6) 由 Mode/列 Access 为 Readonly 触发;wrongType(7)/badValue(3) 由类型不匹配或转换失败触发。
  • **解决方案:**核对接口 Mode、列 Access、ReqBody/Sequence 的 Type 与校验规则。

Q5:如何定制 OEM OID 前缀?

  • **一句话答案:**通过替换关系配置 SnmpOemIdentifier 设置。
  • **根因:**默认 OEM 前缀 1.3.6.1.4.1.2011.2.235.1.1.,组件会替换为 1.3.6.1.4.1.<替换值>.。
  • **解决方案:**设置 SnmpOemIdentifier 并保证映射配置 OID 前缀一致。

项目介绍

Provide openUBMC SNMP interfaces enabling system information query and configuration operations

定制我的领域