SNMP開發系列 (三)SNMP Agent的實現

SNMP在IT運營、網絡設備管理、通信網元管理、物聯網上應用廣泛。以下章節將分析Linux/pSos等嵌入式環境下SNMP Agent流程特點及使用嵌入式設計SNMP Agent的技術細節(其中涉及到信息模型設計、C/C++語言等內容請各位看官提前學習哈)。本章也可以作為嵌入式下SNMP Agent的軟件開發開發指導書。

(一)SNMP協議體系及技術要點

(二)SNMP Agent的設計

(三)SNMP Agent的實現

SNMP開發系列(三)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

返回值:

SNMP開發系列(三)SNMP Agent的實現

注意

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;

SNMP開發系列(三)SNMP Agent的實現

有三個實例,Manager需要進行三次next操作才能完成遍歷;

SNMP開發系列(三)SNMP Agent的實現

注意

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 測試建議

建議的調試配置見下圖:

SNMP開發系列(三)SNMP Agent的實現

SNMP Agent單元調試環境

(全文完)


分享到:


相關文章: