Version 10 of SNMP parser

Updated 2007-03-14 14:09:01

MJ - Because SNMP uses ASN.1 to encode packets, one can use tcllib's asn package to decode the SNMP packets. The code below implements the ::asn::decodeBer command that will create a nested list of the parsed SNMP packet.


 package require asn

 namespace eval asn {

    namespace eval util {

        proc string2hex {string} {
            binary scan $string H* t
            set res [regexp -inline -all {..} $t]
            return [join $res " "]
        }
    }

    array set dispatch {
        02    decodeInteger
        04    decodeOctetString
        06    decodeOID
        30    decodeSequence
        40    decodeNetworkAddress
        43    decodeTimeTicks
        A2    decodeGetResponse-PDU
        A4    decodeTrap-PDU
        A6    decodeInformRequest-PDU
        A7    decodeSNMPv2-Trap-PDU
    }

    proc decodeBer {raw} {
        variable dispatch

        asnGetHexTag raw hex_tag

        if {[info exists dispatch($hex_tag)] } {
            set res [$dispatch($hex_tag) $raw]
            return $res
        } else {
            dputs "Don't know how to handle: $hex_tag"
            asnGetByte raw tag
            asnGetLength raw length
            asnGetBytes raw $length value
            return [list $hex_tag [util::string2hex $value] ]
        }
    }

    proc asnGetHexTag { data_var tag_var} {
        # no destructively get the tag
        upvar $data_var data $tag_var hex_tag
        set orig $data
        asnGetByte data tag
        set hex_tag [format %2.2X $tag]
        dputs "Part has tag $hex_tag"
        set data $orig
    }

    proc asnMorphTag { data_var tag } {
        upvar $data_var data
        asnGetByte data dummy
        set data $tag$data
    }

    proc asnGetElement { data_var element_var } {

        upvar $data_var data $element_var element
        dputs "Getting the first element from: [util::string2hex $data]"
        set orig $data
        asnGetByte data tag
        asnGetLength data length
        asnGetBytes  data $length temp

        set remaining_length [string length $data]
        set element [string range $orig 0 end-$remaining_length]
        dputs "Element is: [util::string2hex $element]"
        return
    }

    proc decodeSequence {raw {type SEQUENCE}} {
        set data $raw
        asnGetSequence data sequence
        while {[string length $sequence] > 0 } {
            asnGetElement sequence element
            lappend result [decodeBer $element]
        }
        if {![info exists result] } { set result {}}
        return [list $type $result]
    }

    proc decodeGetResponse-PDU { raw } {
        # a GetResponse PDU is an implicit sequence so morph it to that
        puts "GetResponse"
        asnMorphTag raw "\x30"
        return [decodeSequence $raw GetResponse-PDU]
    }

    proc decodeTrap-PDU { raw } {
        # a trap PDU is an implicit sequence so morph it to that
        asnMorphTag raw "\x30"
        return [decodeSequence $raw Trap-PDU]
    }
    proc decodeSNMPv2-Trap-PDU { raw } {
        # an SNMPv2 trap PDU is an implicit sequence so morph it to that
        asnMorphTag raw "\x30"
        return [decodeSequence $raw SNMPv2-Trap-PDU]
    }
    proc decodeInformRequest-PDU { raw } {
        # an InformRequest PDU is an implicit sequence so morph it to that
        asnMorphTag raw "\x30"
        return [decodeSequence $raw InformRequest-PDU]
    }

    proc decodeTimeTicks { raw } {
        # a TimeTick is an implicit integer so morph it to that
        asnMorphTag raw "\x02"
        return [decodeInteger $raw TimeTicks]
    }

    proc decodeInteger {raw {type INTEGER}} {
        return [handleWithAsnLib $type asnGetInteger $raw]
    }
    proc decodeOID {raw} {
        return [handleWithAsnLib {OBJECT IDENTIFIER} asnGetObjectIdentifier $raw]
    }

    proc decodeOctetString {raw} {
        return [handleWithAsnLib OCTETSTRING asnGetOctetString $raw]
    }

    proc decodeNetworkAddress {raw} {
        asnGetByte raw dummy
        asnGetByte raw length
        asnGetBytes raw $length address
        foreach number [split $address ""] {
            lappend result [scan $number %c]
        }
        return [list IpAddress $result]
    }

    proc handleWithAsnLib {type libfunction raw} {
        dputs "Decoding $type: [util::string2hex $raw]"
        $libfunction raw result
        return [list $type $result]
    }

 }
 proc dputs {text} {}
 package provide ber 0.1

Sample usage for an SNMPv1 trap PDU

 package require base64
 set trap [join {MIGXAgEABAZwdWJsaWOkgYkGCCsGAQQBgo17QATAqAAzAgEGAgID6EMBZDBtMBoGDCsFAQQBgo17?\
                AQbOEAQKbG9jYWxob3N0IDBPBgwrBQEEAYKNewEGzhEEPyBzdShwYW1fdW5peClbMjUxMTVdOiBz?\
                ZXNzaW9uIG9wZW5lZCBmb3IgdXNlciByb290IGJ5ICh1aWQ9NTAwKQ==} {}]
 ::asn::decodeBer [base64::decode $trap]

gives:

 SEQUENCE {{INTEGER 0} {OCTETSTRING public} {Trap-PDU {{{OBJECT IDENTIFIER} {1 3 6 1 4 1 34555}}\
 {IpAddress {192 168 0 51}} {INTEGER 6} {INTEGER 1000} {TimeTicks 100}\
 {SEQUENCE {{SEQUENCE {{{OBJECT IDENTIFIER} {1 3 5 1 4 1 34555 1 6 10000}} {OCTETSTRING {localhost }}}}\
 {SEQUENCE {{{OBJECT IDENTIFIER} {1 3 5 1 4 1 34555 1 6 10001}}\
 {OCTETSTRING { su(pam_unix)[25115]: session opened for user root by   (uid=500)}}}}}}}}}

MJ - Note that the [asnMorphTag] proc is not needed in recent versions of tcllib anymore, because that provides the (better named) asnRetag which has the same purpose.


Category Example | Category Networking | Category Package