以TCL實現SNMP

由於我很多程式需要用到SNMP的功能,而且我希望能打包成單一直行檔,最簡單的方式是使用netsnmp,將程式重編譯成TCL可以載入的DLL檔即可,不過這一步我始終做不出來,只好走別條路,直接用TCL編輯SNMP封包

SNMP封包包在UDP裡面,內容則是以ASN.1的編碼方式,所以只要使用udp和asn這兩個package就可以實現snmp



SNMP封包格式簡介

由於只用到最簡易的模式,所以只研究了 SNMPv1 與 SNMPv2c (Community-based SNMPv2)

封包格式如下


Message ::=
                     SEQUENCE {
                          version  -- 0 for SMP ; 1 for SNMPv2c
                              INTEGER,
                         community      -- community name
                             OCTET STRING,

                         data           -- e.g., PDUs if trivial
                             ANY        -- authentication is being used
                     }

最外層的格式,SNMPv2c 沿用SNMP的格式,只把version從0改成1

-- SNMP PDUs

PDUs ::=CHOICE {
get-request GetRequest-PDU,
get-next-request GetNextRequest-PDU,
get-response GetResponse-PDU,
set-request SetRequest-PDU,
trap   Trap-PDU
}


-- SNMPv2c PDUs

PDUs ::= CHOICE {
get-request   GetRequest-PDU,
get-next-request  GetNextRequest-PDU,
get-bulk-request  GetBulkRequest-PDU,
response Response-PDU,
set-request  SetRequest-PDU,
inform-request InformRequest-PDU,
snmpV2-trap SNMPv2-Trap-PDU,
report Report-PDU
}


GetRequest-PDU ::= [0] IMPLICIT PDU
GetNextRequest-PDU ::= [1] IMPLICIT PDU
Response-PDU ::= [2] IMPLICIT PDU
SetRequest-PDU ::= [3] IMPLICIT PDU
GetBulkRequest-PDU ::= [5] IMPLICIT BulkPDU
InformRequest-PDU ::= [6] IMPLICIT PDU
SNMPv2-Trap-PDU ::= [7] IMPLICIT PDU


PDU:SNMPv2新增了get-bulk-request,此PDU可一次得到多個結果,在讀取一個具有多個index的MIB時很方便,不需使用多次的getnext,只要送一次封包就能把結果都抓回來,另外也增加了 inform-request這個mangement 與 management 之間溝通的封包


大多數的PDU格式如下



PDU ::= SEQUENCE {
request-id INTEGER (-214783648..214783647),
error-status                -- sometimes ignored
  INTEGER {
  noError(0),
  tooBig(1),
  noSuchName(2),      -- for proxy compatibility
  badValue(3),        -- for proxy compatibility
  readOnly(4),        -- for proxy compatibility
  genErr(5),
  noAccess(6),  -- (6)~(18) 為SNMPv2新增的error staatus,能更準確的描述錯誤信息
  wrongType(7),
  wrongLength(8),
  wrongEncoding(9),
  wrongValue(10),
  noCreation(11),
  inconsistentValue(12),
  resourceUnavailable(13),
  commitFailed(14),
  undoFailed(15),
  authorizationError(16),
  notWritable(17),
  inconsistentName(18)
  },
error-index                 -- sometimes ignored
INTEGER (0..max-bindings),
variable-bindings           -- values are sometimes ignored
VarBindList
}

VBind格式


VarBind ::= SEQUENCE {
name ObjectName,
CHOICE {
  value          ObjectSyntax,
  unSpecified    NULL,    -- in retrieval requests
  -- exceptions in responses
  noSuchObject   [0] IMPLICIT NULL,
  noSuchInstance [1] IMPLICIT NULL,
  endOfMibView   [2] IMPLICIT NULL
}
}

-- variable-binding list
VarBindList ::= SEQUENCE (SIZE (0..max-bindings)) OF VarBind



程式範例

SNMP採用ASN.1編碼,在知道了編碼方式與格式之後,只要使用asn套件將資料一筆一筆包起來就可以了

1.編輯 snmpget  -v 2c -c public 127.0.0.1  1.3.6.1.2.1.1.1.0 的UDP內容

從最內層開始編輯

# vbind由oid與value組成,snmpget時value給Null即可
append vbind [::asn::asnObjectIdentifier [split  1.3.6.1.2.1.1.1.0  .]]
append vbind [::asn::asnNull]
set vbind [::asn::asnSequence $vbind]


# 組成 GetRequest-PDU
append PDU [::asn::asnInteger 123456]
append PDU [::asn::asnInteger 0] ; snmpget時 error-status 與 error-index
append PDU [::asn::asnInteger 0]
append PDU [::asn::asnSequence $vbind]
set PDU [::asn::asnContextConstr 0 $PDU]

# 最後組成 SNMP message
append snmp_packet [::asn::asnInteger 1]  ;# SNMPv2C 版本為 1
append snmp_packet [::asn::asnOctetString public]
append snmp_packet $PDU
set snmp_packet [::asn::asnSequence $snmp_packet]


接下來只要開一個UDP socket指定好IP與Port再用puts把資料送出去即可,並使用fileevent偵測是否有封包回傳,當收到封包後在進行解碼

2. 解析回傳封包


::asn::asnGetSequence data data ;拆掉最外層sequence
::asn::asnGetInteger data version ; 取得版本
::asn::asnGetOctetString data community ; 取得community string
::asn::asnGetContext data PDU_Type ; 取得PDU Type,Response-PDU 為2
# 以下為PDU內容
::asn::asnGetInteger data id ;# 此值應該與傳送的id相同
::asn::asnGetInteger data err ;# 若無錯誤 err 與err_ind都為0
::asn::asnGetInteger data err_ind
# 以下為 VarBindList,每一組值是 oid和value組成的sequence
::asn::asnGetSequence data vbind;# 先拆掉sequence
::asn::asnGetObjectIdentifier data oid ;# 解出oid
# value的部分再根據tag選擇解碼的指令,假設以知為字串
::asn::asnGetOctetString data value ;# 解出字串






2 則留言:

  1. 可以直接引用netsnmp來使用,我一般都是這樣用,exec snmpget -option community ipaddr oid。

    用exec執行外部windows指令。

    回覆刪除
    回覆
    1. 會用這麼麻煩的方法主要是為了收snmp_inform,有了snmp_inform之後其他的封包只是稍微修改而已

      刪除