SNMP Agent添加私有MIB

環境:

系統:Ubuntu 14.04 LTS
snmp版本:net-snmp-5.7.3

step1.編寫自己的MIB文件

這部分內容,網上的資料還是比較多的,自己在挖坑過程中沒有遇到太多問題,不囉嗦了。給出一個自己寫好的MIB文件,裏面包含了一些註釋:

QOS-MIB DEFINITIONS ::= BEGIN 

IMPORTS 
    OBJECT-GROUP, MODULE-COMPLIANCE, NOTIFICATION-GROUP
        FROM SNMPv2-CONF
    enterprises, Integer32, Unsigned32, OBJECT-TYPE, MODULE-IDENTITY,NOTIFICATION-TYPE
        FROM SNMPv2-SMI
    DisplayString
        FROM SNMPv2-TC;

--enterprises就是.1.3.4.1.4.1
-- ::={}格式裏定義的就是當前節點的訪問地址
--.1.3.6.1.4.1.73691
QosMIB MODULE-IDENTITY
    LAST-UPDATED "201803021450Z"
    ORGANIZATION
        ""
    CONTACT-INFO
        "sedwt-zjzhu"
    DESCRIPTION
        "XXX's QOS List MIB."
    ::= { enterprises 73691 }

--定義一個自己的根節點,訪問地址爲 QosMIB.1,也就是
--.1.3.6.1.4.1.73691.1
--這個根節點下面又定義了3個子節點
--SYNTAX是該節點數據類型,可以自己定義,也可以用標準裏定義好的
--MAX-Access是該節點的讀寫屬性,有not-accessible,read-only,read-write,read-create,根據自己需求選擇合適的屬性,read-create比較特殊,會在下一篇博客中單獨講。
Qos OBJECT IDENTIFIER ::= { QosMIB 1 }
    WANDevice OBJECT-TYPE
        SYNTAX Integer32 
        MAX-ACCESS read-write
        STATUS current
        DESCRIPTION
            "參數解釋:廣域網側設備。取值範圍:4 - 4 ,默認值:4"
        ::= { Qos 1 }

    WANConnectionDevice OBJECT-TYPE
        SYNTAX Integer32
        MAX-ACCESS read-write
        STATUS current
        DESCRIPTION
            "參數解釋:廣域網側連接設備。,取值範圍:1 - 1,默認值:1"
        ::= { Qos 2 }

    CID OBJECT-TYPE
        SYNTAX Integer32
        MAX-ACCESS read-write
        STATUS current
        DESCRIPTION
            "參數解釋:CID。取值範圍:2 - 20,默認值:2"
        ::= { Qos 3 }
END 

step2.使用mib2c工具生成c文件

寫好的MIB文件我們放在用戶根目錄/.snmp/MIBS/文件夾中,例如我的目錄爲:
/home/zzj/.snmp/MIBS/QOS-MIB.txt

前一篇博客中,我們已經是搭好了snmp環境的。但是在我過坑的過程中發現,環境的事還沒完,比如這裏的.snmp目錄就是沒有的,需要我們手動創建。
如果你問爲啥非要是這個目錄?其實也一定,哈哈。不過當你後面運行mib2c提示Cannot find module xxx異常時,命令行會輸出掃描過的目錄,其中就包含.snmp這個目錄,你把MIB文件放在它掃描的其他目錄也是闊以滴!

ok,跳過了第一個小坑,咱們接着跳。

按照一般攻略,咱們就要執行mib2c生成c文件了。執行前先which mib2c看看環境變量設置好了沒:

zzj@sed:/home$ which mib2c
/usr/local/snmp/bin/mib2c

good,看來沒問題,接下來就是關鍵的mib2c。執行如下命令:

zzj@sed:/home$ env MIBS="+/home/zzj/.snmp/mibs/QOS-MIB.txt" mib2c Qos
Can't find a configuration file called mib2c.conf
I looked in:
  .
  /usr/local/snmp/
  /usr/local/snmp/share/snmp/mib2c-data
  ./mib2c-conf.d

嗯?看來不太對?沒看人提起過還有mib2c.conf這個文件啊!各種百度、Google,想着可能是和snmpd.conf類似,然而並不是啊。這裏折騰了不少時間,一直沒法轉成c文件。某天我看net-snmp的安裝目錄的local文件,發現這裏面就有mib2c.conf,而且這裏也有mib2c.pl,抱着死馬當活馬醫的心態,在這個目錄下試了一下。終於和網上教程對上了有木有!!如下:

zzj@sed:~/net-snmp/net-snmp-5.7.3/local$ env MIBS="+/home/zzj/.snmp/mibs/QOS-MIB.txt" mib2c Qos
writing to -
mib2c has multiple configuration files depending on the type of
code you need to write.  You must pick one depending on your need.

You requested mib2c to be run on the following part of the MIB tree:
  OID:                              Qos
  numeric translation:              .1.3.6.1.4.1.73691.1
  number of scalars within:         9
  number of tables within:          0
  number of notifications within:   0

First, do you want to generate code that is compatible with the
ucd-snmp 4.X line of code, or code for the newer Net-SNMP 5.X code
base (which provides a much greater choice of APIs to pick from):

  1) ucd-snmp style code
  2) Net-SNMP style code

然後就比較簡單了,如果你英文不太差的話,看看提示,跟着選就行了。這裏第一個選2,後續的就不列了。

(敲黑板)注意啦,劃重點了:
執行mib2c是需要一些其他文件的,例如mib2c.conf。最簡單的執行方法就是在net-snmp解壓目錄/local這個目錄下直接執行命令。

命令是啥?自己去上面翻代碼吧!

終於生成了c文件了,生成的文件也是在local目錄下,懷着激動的心情,我們進入下一階段,修改c文件。打開文件

/*
 * Note: this file originally auto-generated by mib2c using
 *        $
 */

#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>
#include "Qos.h"

/** Initializes the Qos module */
void
init_Qos(void)
{
    const oid WANDevice_oid[] = { 1,3,6,1,4,1,73691,1,1 };
    const oid WANConnectionDevice_oid[] = { 1,3,6,1,4,1,73691,1,2 };
    const oid CID_oid[] = { 1,3,6,1,4,1,73691,1,3 };

  DEBUGMSGTL(("Qos", "Initializing\n"));

    netsnmp_register_scalar(
        netsnmp_create_handler_registration("WANDevice", handle_WANDevice,
                               WANDevice_oid, OID_LENGTH(WANDevice_oid),
                               HANDLER_CAN_RWRITE
        ));
    netsnmp_register_scalar(
        netsnmp_create_handler_registration("WANConnectionDevice", handle_WANConnectionDevice,
                               WANConnectionDevice_oid, OID_LENGTH(WANConnectionDevice_oid),
                               HANDLER_CAN_RWRITE
        ));
    netsnmp_register_scalar(
        netsnmp_create_handler_registration("CID", handle_CID,
                               CID_oid, OID_LENGTH(CID_oid),
                               HANDLER_CAN_RWRITE
        ));
}

int
handle_WANDevice(netsnmp_mib_handler *handler,
                          netsnmp_handler_registration *reginfo,
                          netsnmp_agent_request_info   *reqinfo,
                          netsnmp_request_info         *requests)
{
    int ret;
    /* We are never called for a GETNEXT if it's registered as a
       "instance", as it's "magically" handled for us.  */

    /* a instance handler also only hands us one request at a time, so
       we don't need to loop over a list of requests; we'll only get one. */

    switch(reqinfo->mode) {

        case MODE_GET:
            snmp_set_var_typed_value(requests->requestvb, ASN_INTEGER,
                                     /* XXX: a pointer to the scalar's data */,
                                     /* XXX: the length of the data in bytes */);
            break;

        /*
         * SET REQUEST
         *
         * multiple states in the transaction.  See:
         * http://www.net-snmp.org/tutorial-5/toolkit/mib_module/set-actions.jpg
         */
        case MODE_SET_RESERVE1:
                /* or you could use netsnmp_check_vb_type_and_size instead */
            ret = netsnmp_check_vb_type(requests->requestvb, ASN_INTEGER);
            if ( ret != SNMP_ERR_NOERROR ) {
                netsnmp_set_request_error(reqinfo, requests, ret );
            }
            break;

        case MODE_SET_RESERVE2:
            /* XXX malloc "undo" storage buffer */
            if (/* XXX if malloc, or whatever, failed: */) {
                netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_RESOURCEUNAVAILABLE);
            }
            break;

        case MODE_SET_FREE:
            /* XXX: free resources allocated in RESERVE1 and/or
               RESERVE2.  Something failed somewhere, and the states
               below won't be called. */
            break;

        case MODE_SET_ACTION:
            /* XXX: perform the value change here */
            if (/* XXX: error? */) {
                netsnmp_set_request_error(reqinfo, requests, /* some error */);
            }
            break;

        case MODE_SET_COMMIT:
            /* XXX: delete temporary storage */
            if (/* XXX: error? */) {
                /* try _really_really_ hard to never get to this point */
                netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_COMMITFAILED);
            }
            break;

        case MODE_SET_UNDO:
            /* XXX: UNDO and return to previous value for the object */
            if (/* XXX: error? */) {
                /* try _really_really_ hard to never get to this point */
                netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_UNDOFAILED);
            }
            break;

        default:
            /* we should never get here, so this is a really bad error */
            snmp_log(LOG_ERR, "unknown mode (%d) in handle_WANDevice\n", reqinfo->mode );
            return SNMP_ERR_GENERR;
    }

    return SNMP_ERR_NOERROR;
}
int
handle_WANConnectionDevice(netsnmp_mib_handler *handler,
                          netsnmp_handler_registration *reginfo,
                          netsnmp_agent_request_info   *reqinfo,
                          netsnmp_request_info         *requests)
{
    int ret;
    /* We are never called for a GETNEXT if it's registered as a
       "instance", as it's "magically" handled for us.  */

    /* a instance handler also only hands us one request at a time, so
       we don't need to loop over a list of requests; we'll only get one. */

    switch(reqinfo->mode) {

        case MODE_GET:
            snmp_set_var_typed_value(requests->requestvb, ASN_INTEGER,
                                     /* XXX: a pointer to the scalar's data */,
                                     /* XXX: the length of the data in bytes */);
            break;

        /*
         * SET REQUEST
         *
         * multiple states in the transaction.  See:
         * http://www.net-snmp.org/tutorial-5/toolkit/mib_module/set-actions.jpg
         */
        case MODE_SET_RESERVE1:
                /* or you could use netsnmp_check_vb_type_and_size instead */
            ret = netsnmp_check_vb_type(requests->requestvb, ASN_INTEGER);
            if ( ret != SNMP_ERR_NOERROR ) {
                netsnmp_set_request_error(reqinfo, requests, ret );
            }
            break;

        case MODE_SET_RESERVE2:
            /* XXX malloc "undo" storage buffer */
            if (/* XXX if malloc, or whatever, failed: */) {
                netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_RESOURCEUNAVAILABLE);
            }
            break;

        case MODE_SET_FREE:
            /* XXX: free resources allocated in RESERVE1 and/or
               RESERVE2.  Something failed somewhere, and the states
               below won't be called. */
            break;

        case MODE_SET_ACTION:
            /* XXX: perform the value change here */
            if (/* XXX: error? */) {
                netsnmp_set_request_error(reqinfo, requests, /* some error */);
            }
            break;

        case MODE_SET_COMMIT:
            /* XXX: delete temporary storage */
            if (/* XXX: error? */) {
                /* try _really_really_ hard to never get to this point */
                netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_COMMITFAILED);
            }
            break;

        case MODE_SET_UNDO:
            /* XXX: UNDO and return to previous value for the object */
            if (/* XXX: error? */) {
                /* try _really_really_ hard to never get to this point */
                netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_UNDOFAILED);
            }
            break;

        default:
            /* we should never get here, so this is a really bad error */
            snmp_log(LOG_ERR, "unknown mode (%d) in handle_WANConnectionDevice\n", reqinfo->mode );
            return SNMP_ERR_GENERR;
    }

    return SNMP_ERR_NOERROR;
}
int
handle_CID(netsnmp_mib_handler *handler,
                          netsnmp_handler_registration *reginfo,
                          netsnmp_agent_request_info   *reqinfo,
                          netsnmp_request_info         *requests)
{
    int ret;
    /* We are never called for a GETNEXT if it's registered as a
       "instance", as it's "magically" handled for us.  */

    /* a instance handler also only hands us one request at a time, so
       we don't need to loop over a list of requests; we'll only get one. */

    switch(reqinfo->mode) {

        case MODE_GET:
            snmp_set_var_typed_value(requests->requestvb, ASN_INTEGER,
                                     /* XXX: a pointer to the scalar's data */,
                                     /* XXX: the length of the data in bytes */);
            break;

        /*
         * SET REQUEST
         *
         * multiple states in the transaction.  See:
         * http://www.net-snmp.org/tutorial-5/toolkit/mib_module/set-actions.jpg
         */
        case MODE_SET_RESERVE1:
                /* or you could use netsnmp_check_vb_type_and_size instead */
            ret = netsnmp_check_vb_type(requests->requestvb, ASN_INTEGER);
            if ( ret != SNMP_ERR_NOERROR ) {
                netsnmp_set_request_error(reqinfo, requests, ret );
            }
            break;

        case MODE_SET_RESERVE2:
            /* XXX malloc "undo" storage buffer */
            if (/* XXX if malloc, or whatever, failed: */) {
                netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_RESOURCEUNAVAILABLE);
            }
            break;

        case MODE_SET_FREE:
            /* XXX: free resources allocated in RESERVE1 and/or
               RESERVE2.  Something failed somewhere, and the states
               below won't be called. */
            break;

        case MODE_SET_ACTION:
            /* XXX: perform the value change here */
            if (/* XXX: error? */) {
                netsnmp_set_request_error(reqinfo, requests, /* some error */);
            }
            break;

        case MODE_SET_COMMIT:
            /* XXX: delete temporary storage */
            if (/* XXX: error? */) {
                /* try _really_really_ hard to never get to this point */
                netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_COMMITFAILED);
            }
            break;

        case MODE_SET_UNDO:
            /* XXX: UNDO and return to previous value for the object */
            if (/* XXX: error? */) {
                /* try _really_really_ hard to never get to this point */
                netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_UNDOFAILED);
            }
            break;

        default:
            /* we should never get here, so this is a really bad error */
            snmp_log(LOG_ERR, "unknown mode (%d) in handle_CID\n", reqinfo->mode );
            return SNMP_ERR_GENERR;
    }

    return SNMP_ERR_NOERROR;
}

艾瑪,好長啊,這麼一大堆,怎麼看啊。
其實不難,看到代碼中那些紅色的XXX沒,填空啊!怎麼填?好吧,我還是說一下吧。
以 handle_WANDevice 爲例:
算了還是不說了,註釋那麼清楚了。。。
一般我們對於snmpset的處理都是放在MODE_SET_ACTION中來做的,其他幾個MODE_SET_XXX好像涉及到對象內存申請、釋放啥的,不是很複雜的開發,沒必要動那裏了,都if(0)吧。修改好的c文件就不放了吧,太長了。。

step3.生成動態庫

離成功只有一小步了。c文件也寫好了,現在該想想怎麼讓snmp能調用它。這個網上講解比較多:走到這一步,就只有靜態調用和動態調用兩條路了:
靜態調用就是像安裝net-snmp那樣,重新make,然後make install,這樣的話每次修改都需要給agent重新安裝新變異的snmp,優點是不用修改snmpd.conf。
動態調用就是編譯成動態庫so文件,在snmpd.conf中動態加載。靈活不少。

當前項目中用不到靜態調用,我沒有實驗。一下爲動態調用經驗:
編譯動態庫我也是小白,找到一段可以編譯so庫的MakeFile文件,修改下可用,不解釋:

CC=gcc

FLAGS=-I.`net-snmp-config --cflags` -g
DLFLAGS=-shared -fPIC -g

NAME=Qos
$(NAME).so:$(NAME).c
.PHONY: clean
$(NAME).so: $(NAME).c
    $(CC)$(CFLAGS) $(DLFLAGS) -c -o $(NAME).o $(NAME).c
    $(CC)$(CFLAGS) $(DLFLAGS) -o $(NAME).so $(NAME).o
clean:
    rm-rf *.o *.so

命名makefile文件爲MakeFile_so,放在哪裏隨你自己指定吧,我們就是用make命令來生成so而已。
使用這個makefile的話,c文件,makefile文件,so文件都會在一個目錄下,在這個目錄下執行命令:

make -f MakeFile_so

如果你的c文件有毛病的話,對着命令行的提示改好吧,修改到沒毛病,so就生成了。

step4.配置snmpd.conf加載so文件

打開snmpd.conf文件,在末尾加上

dlmod QosMIB /home/zzj/net-snmp/net-snmp-5.7.3/local/zzj/QosMIB.so

注意:這裏需要Agent設備上支持dlmod,提前查清楚,不要做了無用功。

step5.驗證

打開cmd,執行:

snmpwalk -v2c -c public localhost .1.3.6.1.4.1.73691.1

測試的這個so文件已經沒了,沒法貼結果了,如果你走到這一步,我相信應該是沒有問題的。

全文完

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章