30、实现 SNMP MIB 的详细指南

实现 SNMP MIB 的详细指南

1. 数据查询与获取

在实现 SNMP MIB 时,主要的改动集中在 switch 语句中。通过调用 query() 例程从 Laddie 的 RTA 表中检索值以满足请求。例如, ladVersion 的值来自 Laddie 的 Config 表的 version 字段。由于 Config 表有多个用途不同的行,为了只获取一个结果,添加了 LIMIT 1 OFFSET 1 选项。 LIMIT 1 确保只返回一个答案, OFFSET 1 则指定获取表的第二行。

对于区域数量,直接从 rta_tables 表的 nrows (行数)字段获取。不过,需要使用 query() 的过滤参数来选择 name 字段为 Zone 的行,因为 rta_tables 表为 ladd 守护进程中的每个 RTA 表都有一行记录。

2. 读取警报表

var_ladAlarmTable() 函数用于处理 ladAlarmTable 中对象的读取操作。大部分工作是在 switch 语句中添加代码。读取表的例程与处理标量值的主要区别在于需要处理表行的索引和确定表的大小。

以下是 var_ladAlarmTable() 函数的代码:

unsigned char *
var_ladAlarmTable(struct variable *vp,
                  oid     *name,
                  size_t  *length,
                  int     exact,
                  size_t  *var_len,
                  WriteMethod **write_method)
{
    static long             long_ret;
    static u_long           ulong_ret;
    static u_long           table_size;
    static u_long           table_index;
    static unsigned char    string[SPRINT_MAX_LEN];
    static unsigned char    filter[SPRINT_MAX_LEN];
    static oid              objid[MAX_OID_LEN];
    static struct counter64 c64;

    if (0 == query("rta_tables", "nrows", "name=Zone", "", string, SPRINT_MAX_LEN)) {
        table_size = atol(string);
    } else {
        return NULL;
    }

    if (header_simple_table(vp,
                            name,
                            length,
                            exact,
                            var_len,
                            write_method, 
                            table_size)
                        == MATCH_FAILED )
        return NULL;

    table_index = name[*length-1];
    sprintf(filter, "id=%d", table_index);

    switch(vp->magic) {
    case LADALARMZONENAME:
        DEBUGMSGTL(("LAD", "reading ladAlarmZoneName\n"));
        if (0 == query("Zone", "name", filter, "", string, SPRINT_MAX_LEN)) {
            DEBUGMSGTL(("LAD", "ladAlarmZoneName[%d]=%s\n", table_index, string));
            *var_len = strlen(string);
            return (u_char*) &string;
        }
        break;
    case LADALARMENABLE:
        DEBUGMSGTL(("LAD", "reading ladAlarmEnable\n"));
        if (0 == query("Zone", "enabled", filter, "", string, SPRINT_MAX_LEN)) {
            DEBUGMSGTL(("LAD", "ladAlarmZoneEnable[%d]=%s\n", table_index, string));
            *write_method = write_ladAlarmEnable;
            long_ret = atol(string);
            return (u_char*) &long_ret;
        }
        break;
    case LADALARMLATCHING:
        DEBUGMSGTL(("LAD", "reading ladAlarmLatching\n"));
        if (0 == query("Zone", "latching", filter, "", string, SPRINT_MAX_LEN)) {
            DEBUGMSGTL(("LAD", "ladAlarmZoneLatching[%d]=%s\n", table_index, string));
            *write_method = write_ladAlarmLatching;
            long_ret = atol(string);
            return (u_char*) &long_ret;
        }
        break;
    case LADALARMSTATE:
        DEBUGMSGTL(("LAD", "reading ladAlarmState\n"));
        if (0 == query("Zone", "alarm", filter, "", string, SPRINT_MAX_LEN)) {
            DEBUGMSGTL(("LAD", "ladAlarmZoneState[%d]=%s\n", table_index, string));
            *write_method = write_ladAlarmState;
            long_ret = atol(string);
            return (u_char*) &long_ret;
        }
        break;
    case LADALARMCOUNT:
        DEBUGMSGTL(("LAD", "reading ladAlarmCount\n"));
        if (0 == query("Zone", "count", filter, "", string, SPRINT_MAX_LEN)) {
            DEBUGMSGTL(("LAD", "ladAlarmZoneCount[%d]=%s\n", table_index, string));
            long_ret = atol(string);
            return (u_char*) &long_ret;
        }
        break;
    default:
        ERROR_MSG("");
    }
    return NULL;
}

在每个 case 中,查询 Zone 表的某个字段以获取值。如果查询失败,则跳出 switch 语句并返回 NULL ,表示该值不可检索。对于定义为具有读写访问权限的对象,需要返回一个 write_method ,即处理 SET 操作的例程的指针。

3. 写入警报表

ladAlarmEnable 对象为例,写入例程围绕 switch 语句根据 action 参数进行不同的处理。 switch 语句的 case 通常包括:
- RESERVE1
- RESERVE2
- FREE
- ACTION
- UNDO
- COMMIT

以下是 write_ladAlarmEnable() 函数的代码:

int
write_ladAlarmEnable(int      action,
                     u_char   *var_val,
                     u_char   var_val_type,
                     size_t   var_val_len,
                     u_char   *statP,
                     oid      *name,
                     size_t   name_len)
{
    static long value;
    int         size;
    static int  saved_value;
    static u_long           table_index;
    static unsigned char    string[SPRINT_MAX_LEN];
    static unsigned char    filter[SPRINT_MAX_LEN];

    table_index = name[name_len-1];

    switch ( action ) {
    case RESERVE1:
        if (var_val_type != ASN_INTEGER) {
            DEBUGMSGTL(("LAD", "write to ladAlarmEnable - not ASN_INTEGER\n"));
            return SNMP_ERR_WRONGTYPE;
        }
        if (var_val_len > sizeof(long)) {
            DEBUGMSGTL(("LAD","write to ladAlarmEnable -  bad length\n"));
            return SNMP_ERR_WRONGLENGTH;
        }
        value = *((long *) var_val);
        if (value > 1) {
            DEBUGMSGTL(("LAD", "write to ladAlarmEnable - wrong value %x\n", value));
            return SNMP_ERR_WRONGVALUE;
        }
        DEBUGMSGTL(("LAD", "\nRESERVE1 ok; value is %d\n", value));
        break;
    case RESERVE2:
        size  = var_val_len;
        value = * (long *) var_val;
        DEBUGMSGTL(("LAD", "\nRESERVE2 ok; value is %d\n", value));
        break;
    case FREE:
        DEBUGMSGTL(("LAD", "\nFREE ok; value is %d\n", value));
        break;
    case ACTION:
        DEBUGMSGTL(("LAD", "\nACTION; value is %d\n", value));
        DEBUGMSGTL(("LAD", "writing ladAlarmEnable in row %d\n", table_index));
        sprintf(filter, "id=%d", table_index);
        if (0 != query("Zone", "enabled", filter, "", string, SPRINT_MAX_LEN)) {
            saved_value = -1;
            return SNMP_ERR_RESOURCEUNAVAILABLE;
        }
        saved_value = atol(string);
        sprintf(filter, "id=%d", table_index);
        sprintf(string, "%d", value);
        if (0 != update("Zone", "enabled", filter, string)) {
            return SNMP_ERR_RESOURCEUNAVAILABLE;
        }
        break;
    case UNDO:
        sprintf(filter, "id=%d", table_index);
        sprintf(string, "%d", saved_value);
        if (saved_value != -1) {
            if (0 != update("Zone", "enabled", filter, string)) {
                return SNMP_ERR_RESOURCEUNAVAILABLE;
            }
        }
        break;
    case COMMIT:
        break;
    }
    return SNMP_ERR_NOERROR;
}
  • RESERVE1 :检查值的类型和长度是否准确,并添加了值的范围检查,确保 enable 值只能是 0 或 1。
  • RESERVE2 :保存值的长度和值本身。
  • FREE :释放已分配的资源。
  • ACTION :在写入新值之前,先检索当前值并保存到 saved_value 中,以便在 UNDO 时使用。
  • UNDO :如果 ACTION 成功检索到旧值,则将其写回。
  • COMMIT :由于 ACTION 已经将值写入 RTA 表,所以此步骤无需操作。
4. 修改 Makefile

之前使用 Makefile 生成代理,现在需要对其进行修改以包含 LAD - MIB 代码。以下是修改后的 Makefile

NETSNMP_VERSION = 5.2.1
BUILD_DIR       = ./net-snmp-$(NETSNMP_VERSION)

# Targets
all:  clean setup config build
clean:
        rm -rf $(BUILD_DIR)
        rm -rf /opt/snmp/*
setup:
        tar zxvf net-snmp-$(NETSNMP_VERSION).tar.gz;
        mkdir $(BUILD_DIR)/agent/mibgroup/lad;
        cp ladProject.[ch] $(BUILD_DIR)/agent/mibgroup/lad
config:
        cd $(BUILD_DIR);
        export LDFLAGS="-lpq";
        ./configure --prefix=/opt/snmp --with-mib-modules="lad/ladProject"<../configure.input;
        cd ..   
build:
        cd $(BUILD_DIR);
        make
install:
        cd $(BUILD_DIR);
        install
  • setup :创建 agent/mibgroup/lad 目录,并将 ladProject 的头文件和 C 文件复制到该目录。
  • config :在 LDFLAGS 中添加 -lpq 以告诉链接器包含 libpq 库,同时告诉 configure 包含我们的 MIB 模块。
5. 调试

借助 DEBUGMSGTL 宏,可以跟踪代理的控制流程。调试步骤如下:
1. 停止后台运行的代理:

/etc/rc.d/init.d/snmpd stop
  1. 从命令行运行自己的代理:
/opt/snmp/sbin/snmpd -D "LAD" -Le -f -c /opt/snmp/etc/snmp/snmpd.conf -C
- `-D "LAD"`:激活指定 `LAD` 的 `DEBUGMSGTL` 语句。
- `-Le`:将输出发送到 `stderr`。
- `-f`:防止代理分叉并进入后台,以便在当前终端窗口查看输出。
  1. 在另一个终端窗口使用 snmpget snmpset snmpwalk 查询代理,并观察输出或保存到文件。
6. 陷阱

在 MIB 中定义了两个陷阱,但本文不讨论如何发送这些陷阱。可以参考之前的相关内容,使用 snmptrap 实用工具发送陷阱,或者使用日志事件触发 SNMP 陷阱。需要注意的是,这些陷阱不是由 SNMP 代理生成的,而是通过 ladd 守护进程记录的事件通过日志子系统生成的。

7. 总结

通过以上步骤,我们学习了如何为 Net - SNMP 代理创建 MIB 扩展以实现 MIB,以及如何使用 PostgreSQL 接口库和 RTA 从另一个守护进程检索 MIB 数据值。现在应该能够独立完成这些工作,并且熟悉 MIB 和 Net - SNMP 代理扩展的结构,可以轻松地在 MIB 和代码中添加新对象。但要注意,不能重新分配 OID,应始终在分支末尾添加新对象。

为了检验对这些内容的理解,可以尝试将之前忽略的 Laddie 表的 edge input 列添加到 MIB 和代理中。

以下是读取和写入警报表的流程总结表格:
| 操作 | 步骤 |
| ---- | ---- |
| 读取警报表 | 1. 确定表大小
2. 处理表行索引
3. 根据对象查询 Zone 表获取值 |
| 写入警报表 | 1. RESERVE1 检查值类型和长度
2. RESERVE2 保存值信息
3. FREE 释放资源
4. ACTION 写入新值并保存旧值
5. UNDO 恢复旧值
6. COMMIT 无需操作 |

以下是写入警报表的 mermaid 流程图:

graph TD;
    A[开始] --> B[RESERVE1];
    B -->|检查失败| C[返回错误];
    B -->|检查成功| D[RESERVE2];
    D --> E[FREE];
    E --> F[ACTION];
    F -->|查询失败| C;
    F -->|查询成功| G[保存旧值];
    G --> H[写入新值];
    H -->|写入失败| C;
    H -->|写入成功| I[UNDO];
    I -->|有旧值| J[恢复旧值];
    J -->|恢复失败| C;
    J -->|恢复成功| K[COMMIT];
    K --> L[结束];

实现 SNMP MIB 的详细指南

8. 关键技术点分析
  • 数据查询优化 :在查询 Config 表获取 ladVersion 时,使用 LIMIT 1 OFFSET 1 选项可以避免不必要的数据读取,提高查询效率。对于 rta_tables 表获取区域数量,使用过滤参数选择特定行,减少了查询的数据量。这种针对不同表结构和查询需求进行优化的方式,能有效提升系统性能。
  • 表操作处理 :在读取和写入警报表时,对于表行索引和表大小的处理是关键。通过从 OID 中获取表行索引,并在写入时根据不同的 action 参数进行相应操作,确保了数据的准确读写和错误处理。例如,在 ACTION 步骤中保存旧值,以便在 UNDO 时恢复,保证了数据的一致性。
  • 错误处理机制 :在整个实现过程中,错误处理机制贯穿始终。如在查询失败时返回 NULL ,在写入操作中对不同的错误情况返回相应的错误码,如 SNMP_ERR_WRONGTYPE SNMP_ERR_WRONGLENGTH 等,这些机制能让系统在出现异常时及时反馈,便于调试和维护。
9. 实际应用场景
  • 监控系统 :SNMP MIB 可以用于构建监控系统,通过读取警报表中的数据,如 ladAlarmState ladAlarmCount 等,实时监控系统的运行状态。当出现异常时,可以及时触发警报,通知管理员进行处理。
  • 配置管理 :对于可读写的对象,如 ladAlarmEnable ,可以通过 SNMP 协议远程配置系统的参数,实现对系统的动态管理。
10. 拓展与创新
  • 添加新对象 :根据实际需求,可以在 MIB 文件中添加新的对象。具体步骤如下:
    1. 在 MIB 文件中添加新对象的定义,这主要是一些复制粘贴的工作。
    2. 在定义列表中为新变量定义新的编号。
    3. ladProject_variables 数组中添加相应的行。
    4. 在适当的 switch 语句中添加新的 case 分支,处理新对象的读写操作。
  • 性能优化 :可以进一步优化查询和写入操作,例如使用缓存机制减少对数据库的频繁访问,或者优化 SQL 查询语句以提高查询效率。
11. 常见问题及解决方法
问题 原因 解决方法
查询失败,返回 NULL 数据库连接问题、表结构变化等 检查数据库连接配置,确认表结构是否正确。
写入操作返回错误码 值类型不匹配、长度错误、资源不可用等 根据错误码检查输入值的类型和长度,确保数据库资源可用。
调试时无输出 DEBUGMSGTL 未激活、输出路径设置错误等 检查 -D 选项是否正确设置,确认 -Le 选项指定的输出路径。
12. 总结与展望

通过本文的学习,我们详细了解了实现 SNMP MIB 的全过程,包括数据查询、表的读写操作、Makefile 的修改、调试方法以及陷阱的相关知识。掌握了这些内容后,我们能够独立创建 MIB 扩展,并且可以根据实际需求对其进行拓展和优化。

在未来的应用中,随着网络规模的不断扩大和系统复杂度的增加,SNMP MIB 的重要性将更加凸显。我们可以进一步研究如何更好地利用 SNMP 协议进行系统监控和管理,提高网络的可靠性和性能。同时,也可以探索与其他技术的结合,如大数据分析、人工智能等,为网络管理带来更多的创新和可能性。

以下是添加新对象到 MIB 和代码的流程 mermaid 流程图:

graph TD;
    A[开始] --> B[在 MIB 文件添加对象定义];
    B --> C[定义新变量编号];
    C --> D[在 ladProject_variables 数组添加行];
    D --> E[在 switch 语句添加 case 分支];
    E --> F[结束];

以下是性能优化的步骤列表:
1. 分析系统性能瓶颈,确定需要优化的部分,如查询频繁的表或写入操作耗时较长的对象。
2. 考虑使用缓存机制,如内存缓存或分布式缓存,减少对数据库的直接访问。
3. 优化 SQL 查询语句,避免全表扫描,使用索引提高查询效率。
4. 对代码进行性能测试,验证优化效果,根据测试结果进行调整和改进。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值