SNMP在IT運營、網絡設備管理、通信網元管理、物聯網上應用廣泛。以下章節將分析Linux/pSos等嵌入式環境下SNMP Agent流程特點及使用嵌入式設計SNMP Agent的技術細節(其中涉及到信息模型設計、C/C++語言等內容請各位看官提前學習哈)。本章也可以作為嵌入式下SNMP Agent的軟件開發開發指導書。
(一)SNMP協議體系及技術要點
(二)SNMP Agent的設計
(三)SNMP Agent的實現
1 (三)SNMP Agent的實現
1.1 MIB的創建
創建MIB主要需要完成本設備的MIB庫的編寫以及利用mibcompiler工具來創建MIB庫控制樹代碼。;
利用 mibcompiler 生成控制文件框架,需要生成的文件有:
以sample.mib為例
sample.ctl //控制框架文件 mibcomp –partial –o sample.ctl sample.mib mib.c //MIB樹 mibcomp –mib.c –o mib.c sample.mib sample.ctl leaf.h //葉子節點OID定義 mibcomp –leaf –o leaf.h sample.mib sample.ctl sample.c //訪問函數框架 mibcomp –skel –o sample.c sample.mib sample.ctl mibcomp –stub –o sample.c sample.mib sample.ctl sample.h //訪問函數頭文件 mibcomp –skel.h –o sample.h sample.mib sample.ctl
現在分別描述各部分的開發特點;
1.1.1 設計MIB庫
MIB庫是SNMP Agent 的基礎,可以以從網上獲取一個公共的target.mib為基礎設計自己的MIB庫。
例:sample.mib
-- sample.mib SAMPLE-MIB DEFINITIONS ::= BEGIN --從已有的MIB中引入一些需要的現成數據 IMPORTS OBJECT-TYPE, MODULE-IDENTITY, enterprises FROM SNMPv2-SMI DisplayString FROM SNMPv2-TC; -- asbTndTeam -- FROM ASB-MIB; asbRoot MODULE-IDENTITY LAST-UPDATED "9406072245Z" --"20020322" ORGANIZATION "ASB, Inc." CONTACT-INFO "ASB in ChenDu. Created by Yangyutong email: [email protected] phone: " DESCRIPTION "This MIB is the root MIB for ASB, Inc. enterprise." ::= { enterprises 5555 } –-定位ASB在MIB中的節點位置 asbTndTeam OBJECT IDENTIFIER ::= { asbRoot 1 } asbExtend OBJECT IDENTIFIER ::= { asbRoot 2 } -------------------------- dwdmProduction OBJECT IDENTIFIER ::= { asbTndTeam 1 } -- ochPort OBJECT IDENTIFIER ::= { dwdmProduction 1 } -- omsPort OBJECT IDENTIFIER ::= { dwdmProduction 2 } -- --ochPort葉子節點定義(簡單變量) iID OBJECT-TYPE SYNTAX DisplayString MAX-ACCESS read-only STATUS current DESCRIPTION "ochPort iId." ::= { ochPort 1 } iOperStatus OBJECT-TYPE SYNTAX INTEGER { no(0), yes(1) } MAX-ACCESS read-write STATUS current DESCRIPTION " iOperStatus." ::= { ochPort 2 } -- -- omsPort configurable parameters -- iNeId OBJECT-TYPE SYNTAX INTEGER MAX-ACCESS read-write STATUS current DESCRIPTION " iNeId." ::= { omsPort 1 } --定義表 omsCfgTable OBJECT-TYPE SYNTAX SEQUENCE OF OmsCfgEntry MAX-ACCESS not-accessible STATUS current DESCRIPTION "Table." ::= { omsPort 2 } --表類型申明 omsCfgEntry OBJECT-TYPE SYNTAX OmsCfgEntry MAX-ACCESS not-accessible STATUS current DESCRIPTION "An interface entry." INDEX { omsCfgIndex } ::= { omsCfgTable 1 } --新的數據類型申明 PhyAddr ::=INTEGER{ one(1), two(2), three(3) } OmsCfgEntry ::= SEQUENCE { omsCfgIndex INTEGER, omsPortType INTEGER, mPhyAddr PhyAddr } omsCfgIndex OBJECT-TYPE SYNTAX INTEGER MAX-ACCESS read-only STATUS current DESCRIPTION "Index of this entry." ::= { omsCfgEntry 1 } omsPortType OBJECT-TYPE SYNTAX INTEGER MAX-ACCESS read-only STATUS current DESCRIPTION "Type of this entry." ::= { omsCfgEntry 2 } mPhyAddr OBJECT-TYPE SYNTAX PhyAddr MAX-ACCESS read-only STATUS current DESCRIPTION "IpAddress of this interface." ::= { omsCfgEntry 3 } END
1.1.2 由MIB文件生成MIB控制文件
MIB控制文件是系統用於來關聯變量處理接口的一箇中間文件,它的主要工作是為每種變量組申明處理函數;
方法是:利用 mibcompiler 生成控制文件框架,然後手工添加處理函數申明。
例:
mibcomp –partial –o sample.ctl sample.mib
-- This file was automatically generated by Epilogue Technology's -- Emissary SNMP MIB Compiler, version 9.0. -- This file was generated using the -partial switch. -- -- YOU MAY MODIFY THIS FILE BUT BEWARE ACCIDENTALLY OVERWRITING IT -- BY REGENERATING IT WITH THE MIB COMPILER. -- -- Last build date: Fri Mar 22 13:20:53 2002 -- from file: -- sample.mib sample-ctl-A -- put your FORCE-INCLUDE's here FORCE-INCLUDE FORCE-INCLUDE FORCE-INCLUDE FORCE-INCLUDE FORCE-INCLUDE FORCE-INCLUDE FORCE-INCLUDE FORCE-INCLUDE "sm_types.h" FORCE-INCLUDE "snmpvars.h" FORCE-INCLUDE "samplmib.h" -- -- put your global DEFAULT's here -- DEFAULT set-function-async null_set_async DEFAULT get-function-async get_%n_async DEFAULT next-function-async std_next_async DEFAULT test-function-async it_exists_async DEFAULT cookie (char *)0 -- put your EXCLUDE's here -- DEFINITIONS ::= BEGIN IMPORTS OBJECT-TYPE FROM RFC-1212; -- Below is an OBJECT-TYPE entry for every object in the input MIBs. -- You may position DEFAULT statements at any point in the MIB tree -- by putting them inside the appropriate OBJECT-TYPE declaration. -- You need not modify any to which you don't want to add DEFAULTs. -- (Or, you may remove any OBJECT-TYPE declaration to which you -- don't add DEFAULT statements, as long as you don't add DEFAULT -- statements to any of its descendants that reference its -- descriptor, directly or indirectly.) zeroDotZero OBJECT-TYPE ::= { ccitt 0 } org OBJECT-TYPE ::= { iso 3 } dod OBJECT-TYPE ::= { org 6 } internet OBJECT-TYPE ::= { dod 1 } directory OBJECT-TYPE ::= { internet 1 } mgmt OBJECT-TYPE ::= { internet 2 } mib-2 OBJECT-TYPE ::= { mgmt 1 } transmission OBJECT-TYPE ::= { mib-2 10 } experimental OBJECT-TYPE ::= { internet 3 } private OBJECT-TYPE ::= { internet 4 } enterprises OBJECT-TYPE ::= { private 1 } asbRoot OBJECT-TYPE ::= { enterprises 5555 } asbTndTeam OBJECT-TYPE ::= { asbRoot 1 } dwdmProduction OBJECT-TYPE ::= { asbTndTeam 1 } ochPort OBJECT-TYPE -- DEFAULT set-function-async ochPort_set_async DEFAULT get-function-async ochPort_get_async DEFAULT test-function-async ochPort_test_async ::= { dwdmProduction 1 } iID OBJECT-TYPE ::= { ochPort 1 } iOperStatus OBJECT-TYPE ::= { ochPort 2 } omsPort OBJECT-TYPE -- DEFAULT set-function-async omsPort_set_async DEFAULT get-function-async omsPort_get_async DEFAULT test-function-async omsPort_test_async ::= { dwdmProduction 2 } iNeId OBJECT-TYPE ::= { omsPort 1 } omsCfgTable OBJECT-TYPE -- DEFAULT set-function-async null_set_async DEFAULT get-function-async omsCfgTbl_get_async DEFAULT test-function-async omsCfgTbl_test_async ::= { omsPort 2 } omsCfgEntry OBJECT-TYPE ::= { omsCfgTable 1 } omsCfgIndex OBJECT-TYPE ::= { omsCfgEntry 1 } omsPortType OBJECT-TYPE ::= { omsCfgEntry 2 } mPhyAddr OBJECT-TYPE ::= { omsCfgEntry 3 } asbExtend OBJECT-TYPE ::= { asbRoot 2 } security OBJECT-TYPE ::= { internet 5 } snmpV2 OBJECT-TYPE ::= { internet 6 } snmpDomains OBJECT-TYPE ::= { snmpV2 1 } snmpUDPDomain OBJECT-TYPE ::= { snmpDomains 1 } snmpCLNSDomain OBJECT-TYPE ::= { snmpDomains 2 } snmpCONSDomain OBJECT-TYPE ::= { snmpDomains 3 } snmpDDPDomain OBJECT-TYPE ::= { snmpDomains 4 } snmpIPXDomain OBJECT-TYPE ::= { snmpDomains 5 } snmpProxys OBJECT-TYPE ::= { snmpV2 2 } rfc1157Proxy OBJECT-TYPE ::= { snmpProxys 1 } rfc1157Domain OBJECT-TYPE ::= { rfc1157Proxy 1 } snmpModules OBJECT-TYPE ::= { snmpV2 3 } end
1.1.3 利用MIB生成MIB樹及函數接口
利用上一步生成的控制文件可以生成MIB樹文件和變量接口框架。
方法是:
MIB樹: mib.c mibcomp –mib.c –o mib.c sample.mib sample.ctl 葉子節點OID定義:leaf.h mibcomp –leaf –o leaf.h sample.mib sample.ctl 訪問函數框架: sample.c mibcomp –skel –o sample.c sample.mib sample.ctl mibcomp –stub –o sample.c sample.mib sample.ctl 訪問函數頭文件: sample.h mibcomp –skel.h –o sample.h sample.mib sample.ctl
這幾個文件將作為系統 SNMP Agent代碼的核心;以下的工作將是按我們具體的需求修改這些文件。
1.1.4 為變量定義數據結構
對於簡單變量只需在xxx_get_async()中將該值獲取並填入VB_T參數中即可;
對於表形變量必須定義一個數據結構,包括該表的索引和成員變量,以方便遍歷MIB樹和獲取、設置表變量。
規則:
對錶變量 xxx;
strcut xxxtablereq { type0 xxxindex; //索引 union { type1 element1; //表中的第一個成員 type2 element2; //表中的第二個成員 type3 element3; //表中的第三個成員 。。。 }xxx_union; }; #define element1 xxx_union.element1 #define element2 xxx_union.element2 #define element3 xxx_union.element3 。。。
這樣一個結構對應著一個VarBind,它的index是為了索引各個實例,union的成員就是需要操作的表變量值;
這兒還可以想象實際操作的兩種情況:
首先我們通過一個系統庫函數group_by_getproc_and_instance(),將Manager操作請求中的要操作表節點和變量歸類綁定(VarBind)的第一個VB_T找到。
· Manager要獲取一個葉子的實例;Agent通過對象標識定位到該節點,再通過MIBLOC_T得到葉子的信息,再根據該信息和表的index定位具體的實例值;之後可以通過VB_T中的vb_link指針遍歷當前表該變量的所有實例。
· Manager遍歷的是一個表節點,Agent通過對象標識定位到該節點的第一個變量(葉子),然後Agent可以通過index定位該表的當前實例,並通過VB_T中的vb_link指針遍歷當前表該變量的所有實例。
注意:因為表形變量可能會有多個實例,所以必須為這些表分配空間,一般是在Agent初始化的時候進行;
例:
int xxx_init() { if(NULL == (p_gxxxtable = (struct xxxtablereq*)malloc(MAX_INST * sizeof(struct xxxtablereq)) ) ) return –1; memset(p_gxxxtable, 0, (MAX_INST * sizeof(struct xxxtablereq)) ); return 0; }
1.1.5 編寫變量操作接口函數
變量操作接口函數夫人框架是利用mibcompiler生成的;按類型可以分為test、set、get、next幾類。
Agent程序的開發實際主要集中在這些接口函數的編寫上。
1.1.5.1 test 函數的編寫
test函數在訪問變量前出現,它檢查被訪問變量的實例是否存在。
它的函數聲明類似於:
void xxx_test_async( OIDC_T lastmatch, int compc, OIDC_T* compl, SNMP_PKT_T* pktp, VB_T* vbp) { }
1.OID_T lastmatch
MIB搜索樹中消耗的object identifier 的最後一個單元。
例如:一個變量標識為:1.2.3.4.5.6.7,它的被管理對象是1.2.3.4.5,那麼lastmatch為5,而6.7為實例標識。
該參數在表形變量的處理中特別有用;考慮有一個表對象的標識為:1.2.3.4;對它的成員變量,假設有三個變量它們被標識為:1.2.3.4.1、1.2.3.4.2、1.2.3.4.3;lasrmatch分別是1、2、3;這樣就能很容易得區分和操作它們了。
2.int compc、OID_T* compl
描述變量標識在MIB樹中未消耗的部分。compl為指向剩餘部分的指針,compc為該部分的個數;在大多數時候它們指向的是實例,注意前面講的“lastmatch”,結合起來就將對象和實例分別表現出來了。
仍舉上例:compl應指向6.7,compc值為2。
3.SNMP_PKT_T* pktp,
接收到的包結構指針。
4.VB_T* vbp
當前要操作的VarBind
返回值:
注意:
1. 表形變量,一張表中的變量使用同一test函數;
2. 簡單變量,一般使用庫函數it_exists_async();
test函數樣例:
ip_test_async( OIDC_T lastmatch, int compc, OIDC_T *compl, SNMP_PKT_T *pktp, VB_T *vbp) { VB_T *group_vbp; // 對簡單變量檢查它得結尾是不是.0 if (!((compc == 1) && (*compl == 0))) { testproc_error(pktp, vbp, NO_SUCH_NAME); return; } // 將要操作得VarBind的頭指針找到 group_by_getproc_and_instance(pktp, vbp, compc, compl); // 檢查每一個變量 for (group_vbp = vbp; group_vbp; group_vbp = group_vbp->vb_link) { switch (group_vbp->vb_ml.ml_last_match) { case LEAF_ipForwarding: switch (VB_GET_INT32(group_vbp)) { case VAL_ipForwarding_forwarding: case VAL_ipForwarding_not_forwarding: break; default: // 該表變量不是規定的值則出錯 testproc_error(pktp, group_vbp, WRONG_VALUE); continue; } testproc_good(pktp, group_vbp); break; case LEAF_ipDefaultTTL: testproc_good(pktp, group_vbp); break; default: // 該表變量不是規定的值則出錯 testproc_error(pktp, group_vbp, GEN_ERR); return; } } }
1.1.5.2 next 函數的編寫
next函數用於處理get-next報文,獲得下一個實例的變量值,在get函數前調用。
它的函數聲明一般為:
void xxx_next_async( OIDC_T lastmatch, int compc, OIDC_T *compl, SNMP_PKT_T *pktp, VB_T *vbp) { }
參數的說明同test函數。
Next的工作原理我們用一個例子說明:
假設要遍歷路由表中的子網掩碼,索引是ipAdEntAddr;
有三個實例,Manager需要進行三次next操作才能完成遍歷;
注意:
1. 表形變量,一張表中的變量使用同一next函數;
2. 簡單變量,一般使用庫函數std_next_async();
Next函數示例:
Void ifEntry_next_async( OIDC_T lastmatch, int compc, OIDC_T *compl, SNMP_PKT_T *pktp, VB_T *vbp) { #define ifEntry_INSTANCE_LEN 1 struct mib_ifreq *data, *best; OIDC_T tmp_inst[ifEntry_INSTANCE_LEN]; OIDC_T best_inst[ifEntry_INSTANCE_LEN]; int i; int error, no; unsigned inst_len; unsigned best_inst_len; // 將要操作得VarBind的頭指針找到 group_by_getproc_and_instance(pktp, vbp, compc, compl); // 將表中的所有實例中比compc/compl 大的最小的找出來 best = 0; /* This loop needs to iterate over each entry in your table */ for (data = iftab, no = ifnumber; no; data++,no--) { inst_len = 1; tmp_inst[0] = data->ie_iindex; if ((oidcmp2(inst_len, tmp_inst, compc, compl) > 0) && ((!best || (oidcmp2(inst_len, tmp_inst, inst_len, best_inst) < 0)))) { best = data; for (i = 0; i < inst_len; i++) best_inst[i] = tmp_inst[i]; best_inst_len = inst_len; } } if (best) { // 找到next one ,現在需要得到它的值同時綁定該實例的VarBind for ( ; vbp ; vbp = vbp->vb_link) { if ((error = ifEntry_get_value(vbp->vb_ml.ml_last_match, pktp, vbp, best)) == NO_ERROR) nextproc_next_instance(pktp, vbp, best_inst_len, best_inst); else nextproc_error(pktp, vbp, error); } } else //沒找到next one for ( ; vbp ; vbp = vbp->vb_link ) nextproc_no_next(pktp, vbp); }
1.1.5.3 get函數的編寫
處理get報文,取得變量值。
Get函數可以分為簡單變量和表形變量兩種不同情況。
它們一般的聲明形式如下:
1. 簡單變量:
void xxx_get_async( OIDC_T lastmatch, int compc, OIDC_T *compl, SNMP_PKT_T *pktp, VB_T *vbp) { }
參數說明同test函數。
示例說明:
void interfaces_get_async(OIDC_T lastmatch, int compc, OIDC_T *compl, SNMP_PKT_T *pktp, VB_T *vbp) { int error; // 將要操作得VarBind的頭指針找到 group_by_getproc_and_instance(pktp, vbp, compc, compl); // 檢查是單一葉子還是節點,是葉子則檢查結尾是否.0 if (!((compc == 1) && (*compl == 0))) for ( ; vbp; vbp = vbp->vb_link) getproc_nosuchins(pktp, vbp); else { //非葉子則遍歷之 for ( ; vbp; vbp = vbp->vb_link) { if ((error = interfaces_get_value(vbp->vb_ml.ml_last_match, pktp, vbp)) != NO_ERROR) getproc_error(pktp, vbp, error); } } } //----------------------------------- //獲得當前last_match的值 static int interfaces_get_value(OIDC_T lastmatch, SNMP_PKT_T *pktp, VB_T *vbp) { switch(lastmatch) { case LEAF_ifNumber: // 獲得該變量值 getproc_got_int32(pktp, vbp, (INT_32_T)ifnumber); break; default: return GEN_ERR; } return NO_ERROR; }
2. 表形變量:
void xxxEntry_get_async( OIDC_T lastmatch, Int compc, OIDC_T *compl, SNMP_PKT_T *pktp, VB_T *vbp) { }
參數說明見test函數;
示例說明:
void ifEntry_get_async(OIDC_T lastmatch, int compc, OIDC_T *compl, SNMP_PKT_T *pktp, VB_T *vbp) { struct mib_ifreq *data; int error; // 將要操作得VarBind的頭指針找到 group_by_getproc_and_instance(pktp, vbp, compc, compl); // 按照(compc and compl) 找到該節點在表中的位置 if (ifEntry_lookup(compc, compl, &data) != 0) for ( ; vbp; vbp = vbp->vb_link) getproc_nosuchins(pktp, vbp); else { //遍歷所有變量 for ( ; vbp; vbp = vbp->vb_link) { if ((error = ifEntry_get_value(vbp->vb_ml.ml_last_match, pktp, vbp, data)) != NO_ERROR) getproc_error(pktp, vbp, error); } } }
1.1.5.4 set函數的編寫
處理set報文。
它的一般函數聲明形式為:
void xxx_set_async( OIDC_T lastmatch, int compc, OIDC_T *compl, SNMP_PKT_T *pktp, VB_T *vbp) { }
參數說明見test函數。
示例說明:
void ifEntry_set_async(OIDC_T lastmatch, int compc, OIDC_T *compl, SNMP_PKT_T *pktp, VB_T *vbp) { struct mib_ifreq ifr; long rc; struct mib_ifreq *data = vbp->vb_priv; for ( ; vbp; vbp = vbp->vb_link) { switch (vbp->vb_ml.ml_last_match) { case LEAF_ifAdminStatus: // 具體管理變量 memset(&ifr, 0, sizeof(ifr)); ifr.ie_iindex = data->ie_iindex; ifr.ie_adminstatus = VB_GET_INT32(vbp); // 設置具體值 if (rc = ioctl(mib_sock, SIOCSIFADMINSTATUS, (char *)&ifr)) { perror("ioctl error"); setproc_error(pktp, vbp, COMMIT_FAILED); return; } setproc_good(pktp, vbp); break; default: setproc_error(pktp, vbp, COMMIT_FAILED); return; } } }
接口函數的說明可以看出, SNMP Agent開發實際是比較簡單的;這兒細心的讀者可能已經看到在test、get、set、next流程之間有一個隱患;即,在test於get、set以及next、get之間必須保證實例不發生改變,實際上需要控制系統核心在這期間關閉任務搶佔位。
1.2 測試建議
建議的調試配置見下圖:
(全文完)