SNMPマネージャの開発
「実践SNMP教科書」の第9章の復刻版です。
2024年の復刻時点では、Zabbixなどのオープンソースソフトなどがあります。TWSNMPもFCやシン・TWSNMPなど復刻版があります。
SNMPマネージャの構成
SNMPアクセス方式
同期アクセス方式
#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <string.h>
/* SNMPv3を使用しない場合は、この定義を削除 */
#define DEMO_USE_SNMP_VERSION_3
#ifdef DEMO_USE_SNMP_VERSION_3
const char *our_v3_passphrase = "The UCD Demo Password";
#endif
int main(int argc, char ** argv)
{
struct snmp_session session, *ss;
struct snmp_pdu *pdu;
struct snmp_pdu *response;
oid anOID[MAX_OID_LEN];
size_t anOID_len = MAX_OID_LEN;
struct variable_list *vars;
int status;
int count=1;
/*
* SNMPライブラリの初期化
*/
init_snmp("snmpapp");
/*
* セッションの初期化
*/
snmp_sess_init( &session ); /* デフォルトに設定 */
session.peername = strdup("test.net-snmp.org");
/* 認証パラメータの設定 */
#ifdef DEMO_USE_SNMP_VERSION_3
/* SNMPv3使用の場合 */
/* SNMPバージョン番号の設定 */
session.version=SNMP_VERSION_3;
/* SNMPv3ユーザ名の設定 */
session.securityName = strdup("MD5User");
session.securityNameLen = strlen(session.securityName);
/* セキュリティレベルの設定 */
session.securityLevel = SNMP_SEC_LEVEL_AUTHNOPRIV;
/* 認証プロトコルをMD5に設定 */
session.securityAuthProto = usmHMACMD5AuthProtocol;
session.securityAuthProtoLen = sizeof(usmHMACMD5AuthProtocol)/sizeof(oid);
session.securityAuthKeyLen = USM_AUTH_KU_LEN;
/* 認証パスワードを設定 */
if (generate_Ku(session.securityAuthProto,
session.securityAuthProtoLen,
(u_char *) our_v3_passphrase, strlen(our_v3_passphrase),
session.securityAuthKey,
&session.securityAuthKeyLen) != SNMPERR_SUCCESS) {
snmp_perror(argv[0]);
snmp_log(LOG_ERR,
"Error generating Ku from authentication pass phrase. \n");
exit(1);
}
#else /* SNMPv1の場合 */
/* SNMPバージョン番号を設定 */
session.version = SNMP_VERSION_1;
/* set the SNMPv1 community name used for authentication */
session.community = "demopublic";
session.community_len = strlen(session.community);
#endif /* SNMPv1 */
/*
* セッションをオープン
*/
SOCK_STARTUP; //これは、Windowsの場合、WinSockの初期化を行う。
ss = snmp_open(&session); /* セッション */
if (!ss) {
snmp_perror("ack");
snmp_log(LOG_ERR, "セッションオープンエラー!\n");
exit(2);
}
/*
* PDUの作成
* 1) system.sysDescr.0をGET
*/
pdu = snmp_pdu_create(SNMP_MSG_GET);
read_objid(".1.3.6.1.2.1.1.1.0", anOID, &anOID_len);
#if OTHER_METHODS
// オブジェクト名の変換は、他に以下の方法がある。
get_node("sysDescr.0", anOID, &anOID_len);
read_objid("system.sysDescr.0", anOID, &anOID_len);
#endif
snmp_add_null_var(pdu, anOID, anOID_len);
/*
* リクエストの送信と応答待ち
*/
status = snmp_synch_response(ss, pdu, &response);
/*
* 応答の処理
*/
if (status == STAT_SUCCESS && response->errstat == SNMP_ERR_NOERROR) {
/*
* 成功: 応答を表示
*/
for(vars = response->variables; vars; vars = vars->next_variable)
print_variable(vars->name, vars->name_length, vars);
/* 情報の表示処理 */
for(vars = response->variables; vars; vars = vars->next_variable) {
if (vars->type == ASN_OCTET_STR) {
char *sp = (char *)malloc(1 + vars->val_len);
memcpy(sp, vars->val.string, vars->val_len);
sp[vars->val_len] = '\0';
printf("値 #%d は文字列: %s\n", count++, sp);
free(sp);
} else
printf("値はe #%d 文字列ではない! \n", count++);
}
} else {
/*
* 失敗: エラー表示
*/
if (status == STAT_SUCCESS)
fprintf(stderr, "エラーコード: %s\n",
snmp_errstring(response->errstat));
else
snmp_sess_perror("snmpget", ss);
}
/*
* 終了処理:
* 1) 応答の開放.
* 2) セッションのクローズ.
*/
if (response)
snmp_free_pdu(response);
snmp_close(ss);
SOCK_CLEANUP;
return (0);
} /* main() */
非同期アクセス方式
TWSNMPのSNMPアクセスクラスライブラリ
class CSnmpApi : public CObject
{
public:
// タイマーコールバックルーチン
static void CALLBACK TimerCallBack(HWND hWnd,UINT uMsg,UINT_PTR idEvent,DWORD dwTime);
// ユーティリティ関数、
static CString GetVBStr(char *p,BOOL bOid);
static CString GetSubTree(struct tree *tree);
static CString GetTrapDescr(int wTrap);
static CString ConvShortNameVarBind(CString szIn);
static CString GetTypeName(int wType );
static int GetIndexList(CString& szIn,CString szMib,CStringList& slIndex);
static CString GetIPAddr(CString &szIPList, CString szIndex);
static double GetDoubleMibVal(CString & szIn,CString szMib);
static double GetDoubleMibVal(CString & szVal);
static CString GetShortName( CString &sIn);
static CString GetOid(char *pLabel);
static int GetIntMibVal(CString &sIn,CString szMib);
static int GetIntMibVal(CString & szVal);
static CString GetMibVal( CString &sIn,CString szMib);
// SNMPライブラリの初期化/開放
static void CloseSnmpApi(CString szType);
static void InitSnmpApi(CString szType,CString szConfDir,CString szMibDir,CString szLogFile ="");
// TRAP送受信
void StopTrapRcv(void);
BOOL SendTrap(CString szIP,CString szCom,CString szEID,CString szVarBindList,int wGen,int wSpe);
CString GetTrap(void);
BOOL StartTrapRcv(int wPort=162);
CStringList m_TrapList;
UINT m_wTrapPort;
static int CSnmpApi::TrapRcv(int op,struct snmp_session *session,int reqid,struct snmp_pdu *pdu,void *magic);
// SNMPアクセスメッソド
CString GetRetStr(void);
int m_wReqType;
void SetVarList(CString& szVarList,CString szSep);
void ClearReq(void);
// リクエストコールバックルーチン
static int CSnmpApi::ReqCallback(int op,struct snmp_session *session,int reqid,struct snmp_pdu *pdu,void *magic);
BOOL Walk(int nMode,CString szIP,CString szComOrUser,CString szPasswd,CString szVarList,int wTimeOut = 50,int wRetry =2);
BOOL SetReq(int nMode,CString szIP,CString szComOrUser,CString szPasswd,CString szVarList,int wTimeOut = 50,int wRetry =2);
BOOL GetReq(int nMode,CString szIP,CString szComOrUser,CString szPasswd,CString szVarList,int wTimeOut = 50,int wRetry =2);
BOOL GetNextReq(int nMode,CString szIP,CString szComOrUser,CString szPasswd,CString szVarList,int wTimeOut = 50,int wRetry =2);
BOOL SendSetReq(void);
BOOL SendReq(void);
// セッション変数とパラメータ
struct snmp_session *m_pSnmpSession;
unsigned int m_wTimeOut;
unsigned int m_wRetry;
CString m_szIP;
unsigned int m_wTime;
int m_wStatus;
int m_wRetCode;
oid m_RootOID[MAX_OID_LEN];
size_t m_nRootOIDLen;
CStringList m_slVarList;
CStringList m_slRet;
BOOL m_bMIBOver;
CSnmpApi();
virtual ~CSnmpApi();
int m_nSnmpMode;
CString m_szPasswd;
CString m_szComOrUser;
};
①初期化
void CSnmpApi::InitSnmpApi(CString szType, CString szConfDir, CString szMibDir, CString szLogFile)
{
CString s;
//SNMPライブラリに必要な環境変数の登録
s.Format("MIBDIRS=%s",szMibDir);
_putenv(s);
s.Format("SNMPCONFPATH=%s",szConfDir);
_putenv(s);
s.Format("SNMP_PERSISTENT_FILE=%s\\%s.tmp",szConfDir,szType);
_putenv(s);
char szType2[256];
strcpy(szType2,szType);
//SNMPライブラリの初期化
init_snmp(szType2);
//受信確認のためのタイマーコールバックルーチンの登録
nIDTimer = ::SetTimer(NULL,123,5,TimerCallBack);
}
②リクエスト送信
BOOL CSnmpApi::SendReq()
{
if( m_slVarList.IsEmpty() ) {
m_wRetCode = SNMP_INTERROR;
m_wStatus = SNMP_DONE;
return FALSE;
}
m_wStatus = SNMP_DOING;
struct snmp_session session;
struct snmp_pdu *pdu;
oid name[MAX_OID_LEN];
size_t name_length;
int status;
char szIP[64];
char szComOrUser[256];
char szPasswd[256];
m_slRet.RemoveAll();
//セッションの初期化
snmp_sess_init(&session);
session.local_port = SNMP_DEFAULT_REMPORT;
session.callback = ReqCallback;
session.callback_magic = this;
session.authenticator = NULL;
session.retries = m_wRetry; /* リトライ. */
session.timeout = m_wTimeOut*1000; /* タイムアウト(uSec単位) */
strcpy(szIP,m_szIP);
session.peername = szIP; //宛先
// SNMPのバージョンによって、Community又は、ユーザ名、パスワードを設定
strcpy(szComOrUser,m_szComOrUser);
strcpy(szPasswd,m_szPasswd);
switch (m_nSnmpMode ) {
case SNMP_MODE_V1:
session.version = SNMP_VERSION_1;
session.community = (unsigned char*)szComOrUser;
session.community_len = strlen(szComOrUser);
break;
case SNMP_MODE_V2C:
session.version = SNMP_VERSION_2c;
session.community = (unsigned char*)szComOrUser;
session.community_len = strlen(szComOrUser);
break;
case SNMP_MODE_V3_ANP:
session.version = SNMP_VERSION_3;
session.securityLevel = SNMP_SEC_LEVEL_AUTHNOPRIV;
session.securityAuthProto = usmHMACMD5AuthProtocol;
session.securityAuthProtoLen = USM_AUTH_PROTO_MD5_LEN;
session.securityName = szComOrUser;
session.securityNameLen = strlen(szComOrUser);
session.securityAuthKeyLen = USM_AUTH_KU_LEN;
if (generate_Ku(session.securityAuthProto,
session.securityAuthProtoLen,
(u_char *) szPasswd, strlen(szPasswd),
session.securityAuthKey,
&session.securityAuthKeyLen) != SNMPERR_SUCCESS) {
m_wRetCode = SNMP_INTERROR;
m_wStatus = SNMP_DONE;
return FALSE;
}
break;
default:
m_wRetCode = SNMP_INTERROR;
m_wStatus = SNMP_DONE;
return FALSE;
}
/*
* SNMPセッションのオープン
*/
m_pSnmpSession = snmp_open(&session);
if (m_pSnmpSession == NULL){
m_wRetCode = SNMP_INTERROR;
m_wStatus = SNMP_DONE;
return FALSE;
}
/*
* PDUの作成
*/
pdu = snmp_pdu_create(m_wReqType == SNMP_GET ? SNMP_MSG_GET: SNMP_MSG_GETNEXT);
while( !m_slVarList.IsEmpty() ) {
CString s = m_slVarList.RemoveHead();
char szName[1024];
strcpy(szName,s);
name_length = MAX_OID_LEN;
if (!snmp_parse_oid(szName, name, &name_length)) {
m_wRetCode = SNMP_INTERROR;
m_wStatus = SNMP_DONE;
snmp_free_pdu(pdu);
snmp_close(m_pSnmpSession);
m_pSnmpSession = NULL;
return (FALSE);
} else {
snmp_add_null_var(pdu, name, name_length);
}
}
/*
* リクエスト送信
*
*/
status = snmp_async_send(m_pSnmpSession,pdu,ReqCallback,this);
if(status == 0 && pdu ) snmp_free_pdu(pdu);
return(status > 1);
}
③コールバックルーチン/応答受信または、タイムアウト時に呼ばれる。
応答からの動作モードに応じた管理情報の切り出しなどを行う。
int CSnmpApi::ReqCallback(int op,struct snmp_session *session,int reqid,struct snmp_pdu *pdu,void *magic)
{
CSnmpApi* p = (CSnmpApi*)magic;
if( p == 0 ) return(1);
struct variable_list *vars;
char szTmp[2048];
//opには、コールバックの理由が設定されている。
switch (op ) {
// SNMPメッセージ受信
case NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE:
{
// エラーなし
if (pdu->errstat == SNMP_ERR_NOERROR){
//WALKモードの場合、終了を判定し、必要ならば、次のリクエストを送信する。
if( p->m_wReqType == SNMP_WALK ) {
vars = pdu->variables;
if( vars &&
( vars->type != SNMP_ENDOFMIBVIEW) &&
(vars->type != SNMP_NOSUCHOBJECT) &&
(vars->type != SNMP_NOSUCHINSTANCE)) {
snprint_variable(szTmp,sizeof(szTmp),vars->name, vars->name_length, vars);
if( netsnmp_oid_is_subtree(p->m_RootOID,p->m_nRootOIDLen,vars->name,vars->name_length) ==0 ) {
p->m_slRet.AddTail(p->GetVBStr(szTmp,vars->type == ASN_OBJECT_ID));
struct snmp_pdu *npdu = snmp_pdu_create(SNMP_MSG_GETNEXT);
if( npdu ) {
snmp_add_null_var(npdu, vars->name, vars->name_length);
if( snmp_async_send(session,npdu,ReqCallback,p) > 1) {
return(1);
}
}
}
}
if( !p->m_slRet.IsEmpty() ) {
p->m_wRetCode = SNMP_NOERROR;
} else {
p->m_wRetCode = SNMP_NOSUCHNAME;
}
p->m_wStatus = SNMP_DONE;
} else {
// WALKモード以外は、取得した値を保存して終了
for(vars = pdu->variables; vars; vars = vars->next_variable){
snprint_variable(szTmp,sizeof(szTmp),vars->name, vars->name_length, vars);
p->m_slRet.AddTail(p->GetVBStr(szTmp,vars->type == ASN_OBJECT_ID));
}
p->m_wRetCode = SNMP_NOERROR;
p->m_wStatus = SNMP_DONE;
}
} else {
if (pdu->errstat < 5){
p->m_wRetCode = pdu->errstat;
p->m_wStatus = SNMP_DONE;
} else {
p->m_wRetCode = SNMP_GENERROR;
p->m_wStatus = SNMP_DONE;
}
} /* endif -- SNMP_ERR_NOERROR */
}
break;
case NETSNMP_CALLBACK_OP_TIMED_OUT:
p->m_wRetCode = SNMP_TIMEOUT;
p->m_wStatus = SNMP_DONE;
break;
case NETSNMP_CALLBACK_OP_SEND_FAILED:
p->m_wRetCode = SNMP_TIMEOUT;
p->m_wStatus = SNMP_DONE;
break;
default:
p->m_wRetCode = SNMP_INTERROR;
p->m_wStatus = SNMP_DONE;
break;
}
return 1;
}
ここまでは、古いTWSNMPやNET-SNMPの話です。2024年時点で開発しているTWSNMP FC/FKでは、GO言語のSNMPパッケージ
を使っています。
MIBアクセス方式
SNMPマネージャがエージェントからMIBを取得する場合、管理アプリケーションの目的によって、違った取得方法を使用します。
ポーリング
機器監視のためのポーリングでは、予め取得したいMIBのインスタンスが分かっているので、通常は、Getリクエストでアクセスします。
検索アクセス
ポーリングするMIBのインスタンスを登録する時に、ネットワークI/Fなどのようにノードによってインスタンスの数が変わる場合、インスタンスを検索する必要があります。インスタンスの検索は、GetNextを取得したインスタンスを元に繰り返し実施するsnmpwalkのような方法で行います。リスト9-5にネットワークI/Fの検索例を示します。リストの例では、対象ノードがソフトウェアループバックと、イーサネットの2つのI/Fを持っていることが分かります。この情報を元に、インデックス2番のイーサネットのI/Fに関するポーリングを登録すればよいと判断できます。
ネットワークI/Fの検索例
# snmpwalk -v 2c -c public localhost ifType
RFC1213-MIB::ifType.1 = INTEGER: softwareLoopback(24)
RFC1213-MIB::ifType.2 = INTEGER: ethernet-csmacd(6)
テーブルアクセス
数値形式のMIBの取得方式
ifInTotalPkts=ifInUcastPkts+ifInNUcastPkts+ifInDiscards+ifInErrors.....
ifInErrorRate = (ifInErrors/ifInTotalPkts) * 100
管理アプリケーション
管理MAPとポーリング
シン・TWSNMP(TWSNMP FK)では、
のように進化しました。
自動発見
TRAP受信
ログ機能
グラフ機能
パネル表示
シン・TWSNMPでのパネルは3Dに表示になりました。
MIBブラウザ
MIBに特化した管理アプリケーション
シン・TWSNMPでは、RMON、ホストリソースなどにも対応しています。
ここから先は
実践SNMP教科書 復刻版
20年近く前に書いた「実践SNMP教科書」を現在でも通用する部分だけ書き直して復刻するマガジンです。最近MIBの設計で困っている人に遭遇し…
開発のための諸経費(機材、Appleの開発者、サーバー運用)に利用します。 ソフトウェアのマニュアルをnoteの記事で提供しています。 サポートによりnoteの運営にも貢献できるのでよろしくお願います。