snmp:基于 openUBMC 与 D-Bus 的 BMC 协议适配项目

Provide openUBMC SNMP interfaces enabling system information query and configuration operations

分支3Tags0

snmp

snmp 组件为 openUBMC 提供 SNMP 协议接入能力:以 net-snmp 为载体实现 SNMP Agent,将网络侧 SNMP 管理器(Manager)的 Get/Set 请求通过「映射配置」转换为对 openUBMC 资源协作接口(D-Bus 资源协作接口)的读写操作。


1. 组件概述

snmp 是一个 library 类型组件,最终产物为共享库 libsnmp.so(部署于 /usr/lib64/libsnmp.so),由 net-snmp 的 snmpd 进程加载运行。组件本身不提供 D-Bus 服务、不直接处理 IPMI 命令,而是作为 SNMP 协议与资源协作接口之间的「协议代理 / 适配层」:将标准 SNMP Get/Set 请求映射到资源协作接口对象读写,新增 SNMP 可管理对象时无需修改 C 代码,只需新增一份映射配置。

核心功能:

  • 接口注册:依据映射配置把每条 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 前缀。

与外部系统的交互边界:

SNMP Manager ──SNMP v1/v2c/v3──> net-snmp Agent(snmpd) ──> libsnmp.so(本组件)
                                                              │ route_mapper 按 interface_config 映射配置路由
                                                              ▼
                                                    资源协作接口 /bmc/kepler/... ──> 各微组件服务

2. API 使用说明和示例

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

2.1 match(uri, method, address, username, reqbody) —— 核心入口

接收一条 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/mock_routemapper.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. 组件扩展案例

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

扩展点:

(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。

映射配置文件结构:

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")),经统一日志框架输出。本组件为 library 类型,未注册自定义「一键收集 Dump」回调,一键收集到的是组件输出的标准日志与下述 syslog 记录,无额外自定义文件。

关键日志信息:

日志 含义
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. 问题定界指南

错误码(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 类型错误(请求数据类型与配置不符)

典型问题与定界:

现象 归属判断 关键日志/依据
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 等初始化日志

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


6. FAQ

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 配置即可。 根因:组件经进程内路由映射器读取 interface_config/<product> 配置根(mapping_config/script/plugins)动态注册。 解决方案:按 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 前缀一致。


7. 映射器机制(进程内路由映射器)

snmp 已接入新版「进程内路由映射器」(libroute_mapper + libmcpp 提供的路由映射机制):不再依赖旧的 route_mapper.* / libroutemapper.* 运行库与独立 worker 服务,而是在 snmpd 进程内直接创建路由映射器实例, 复用组件已有的 SDBus(同步阻塞模式)完成 MDB、认证、策略与 Task 请求。

接入内容:

  • src/lualib/snmp_mapper.lua:SNMP 侧映射器门面。基于 routemapper.sync 在进程内加载映射配置, 提供 execute / describe / list_routes / reload 等能力,并把映射器错误统一规范化为 SNMP 错误消息 (MessageName / Message / SnmpStatusCode)。snmp 进程没有 skynet 调度器,映射器调用统一走 *_async 接口 + mc.actor.step() 驱动循环 + take_ready 的同步等待模式(await), 等待期间驱动本 VM 的 actor 队列以执行 Script/Plugin 等扩展回调。
  • src/lualib/config.lua:通过 list_routes() + describe()(编译后的公开元数据)构建接口资源表, 不再访问映射器内部路由表。
  • src/lualib/interface.lua、task_mgmt.lua:请求执行改为 mapper:execute(),任务信息取自执行结果 effects.Tasks,不再依赖旧 mapper:match() 返回的 extra_info。
  • 通用 MDB 访问、SnmpOemIdentifier 占位符替换({{SnmpOemIdentifier}})由映射器内部完成; 组件自身读取 config.json 的 SnmpOemIdentifier 仅用于 URI 校验。

配置根目录语义:进程内路由映射器的 load() 接收部署配置根目录(mapping_config、script、plugins 的公共父目录),即 /opt/bmc/apps/snmp/interface_config[/<platform_id>_<board_id>],不再传 mapping_config 子目录。

运行依赖:设备上需要安装与目标工具链匹配的新版 libroute_mapper 与 libmcpp 运行包,至少包括:

  • /usr/lib64/libroute_mapper_runtime.so;
  • /usr/lib64/routemapper/core.so;
  • /usr/share/lua/routemapper/init.lua、runtime.lua、host.lua、sync.lua、sdbus_transport.lua(及 skynet.lua、cjson.lua);
  • 底层 libmcpp 运行库(mcbase/mcengine/mcapp/mcdbus/lmc 等)。

验证:启动 snmpd 后日志应出现 initialize in-process route mapper successfully,且不再加载旧的 route_mapper.worker_service;单元测试 test/unit 通过 mock 路由映射器门面(test/unit/mock_routemapper.lua) 校验接口注册、请求体、主键与 Sequence 约束。

项目介绍

Provide openUBMC SNMP interfaces enabling system information query and configuration operations

定制我的领域