Code: Select all
;
; snmp.pbi
;
; 1.22 added SNMP version and Counter64
; 1.21 extended BER_Integer()
; 1.20 use lists instead of fixed sized arrays
; 1.11 fix for unicode
; 1.10 added negative integers
; 1.01 fixed RequestID bug
; 1.00 initial release based on the code of Michael Vogel
; Define
CompilerIf #PB_Compiler_IsMainFile
EnableExplicit
CompilerEndIf
; Alive state
Enumeration
#Station_Unknown
#Station_Down
#Station_Up
EndEnumeration
; SNMPVersion
Enumeration
#SNMPv1
#SNMPv2c
EndEnumeration
; PDUType
Enumeration
#PDU_GET
#PDU_GET_NEXT
#PDU_RESPONSE
#PDU_SET
#PDU_TRAP
EndEnumeration
; Error
Enumeration
#ERRORSTATUS_NOERROR
#ERRORSTATUS_TOOBIG
#ERRORSTATUS_NOSUCHNAME
#ERRORSTATUS_BADVALUE
#ERRORSTATUS_READONLY
#ERRORSTATUS_GENERR
#ERRORSTATUS_NOACCESS
#ERRORSTATUS_WRONGTYPE
#ERRORSTATUS_WRONGLENGTH
#ERRORSTATUS_WRONGENCODING
#ERRORSTATUS_WRONGVALUE
#ERRORSTATUS_NOCREATION
#ERRORSTATUS_INCONSISTENTVALUE
#ERRORSTATUS_RESOURCEUNAVAILABLE
#ERRORSTATUS_COMMITFAILED
#ERRORSTATUS_UNDOFAILED
#ERRORSTATUS_AUTHORIZATIONERROR
#ERRORSTATUS_NOTWRITABLE
#ERRORSTATUS_INCONSISTENTNAME
EndEnumeration
; Status
Enumeration
#SNMP_Unknown = -1
#SNMP_Ok
#SNMP_Sent
#SNMP_Received
#SNMP_Error
#SNMP_Error_OID
#SNMP_Error_RID
#SNMP_Error_Name
EndEnumeration
#ClassUniversal = $00
#ClassApplication = $40
#ClassContextSpecific = $80
#ClassPrivate = $C0
#TypePrimitive = $00
#TypeConstructed = $20
;ValueType UniversalClass
#TypeUnknown = 0
#TypeBoolean = 1
#TypeInteger = 2
#TypeBitString = 3
#TypeOctetString = 4
#TypeNull = 5
#TypeObjectIdentifier = 6
#TypeSequence = 16
;ValueType ApplicationClass
#TypeNetworkAddress = 0
#TypeCounter = 1
#TypeGauge = 2
#TypeTimeTicks = 3
#TypeOpaque = 4
#TypeCounter64 = 6
#TypeUInt = 7
; internally used
Enumeration
#Field_Header
#Field_Version
#Field_Community
#Field_PDU
#Field_RequestID
#Field_ErrorState
#Field_ErrorIndex
#Field_VariableLengthWithHeader
#Field_VariableLengthWithoutHeader
#Field_OID
#Field_Variable
#Field_Nil
EndEnumeration
;#SNMP_MaxPacketSize = 548
#SNMP_MaxPacketSize = 1500
CompilerIf #PB_Compiler_OS = #PB_OS_Linux
#SOCKET_ERROR = -1
CompilerEndIf
Structure Reference
value.l
EndStructure
Structure StationType
IP.s
ReadCommunity.s
WriteCommunity.s
SNMPVersion.i
Alive.w
EndStructure
Structure SNMPType
StationNumber.w
PDUType.w
RequestID.l
OID.s
ResponseOID.s
ValueType.w
Value.s
Status.w
EndStructure
Global NewList SNMPStation.StationType()
Global NewList SNMP.SNMPType()
; EndDefine
Procedure.i BER_Length(*memory, *pos.Reference)
; Calculates length of actual field...
Protected byte.a, length.l, n.i
byte = PeekA(*memory + *pos\Value)
*pos\Value + 1
If byte & $80; multi-byte length
byte & $3 ; number of bytes for length information
While byte
length << 8
length + (PeekA(*memory + *pos\Value))
byte - 1
*pos\Value + 1
Wend
Else
length = byte & $7f
EndIf
;Debug "Len: "+Str(length)
ProcedureReturn length
EndProcedure
Procedure.q BER_Integer(*memory, length.i)
; Encodes memory to integer value...
Protected n.q, Neg.i, i.i
If PeekA(*memory) & $80
Neg = #True
EndIf
i = length
While i
n << 8
n | PeekA(*memory)
i - 1
*memory + 1
Wend
If Neg
Select length
Case 1 : n = 0 - ($FF - n)
Case 2 : n = 0 - ($FFFF - n)
Case 3 : n = 0 - ($FFFFFF - n)
Case 4 : n = 0 - ($FFFFFFFF - n)
Case 5 : n = 0 - ($FFFFFFFFFF - n)
Case 6 : n = 0 - ($FFFFFFFFFFFF - n)
Case 7 : n = 0 - ($FFFFFFFFFFFFFF - n)
Case 8 : n = 0 - ($FFFFFFFFFFFFFFFF - n)
EndSelect
EndIf
ProcedureReturn n
EndProcedure
Procedure.s BER_ObjectIDentifier(*memory, length.i)
; Encodes Memory to OID string...
Protected byte.a, ID.i, i.i, OID$
While i < length
byte = PeekA(*memory + i)
If ID & $FE000000
OID$ + ".???"
Break
EndIf
ID << 7 ; ID -> helpbuffer if ID is > 128
ID | (byte & $7F) ; only the lower 7 bits are valid, 8th bit is indicator for 'more is following'
If byte & $80 = 0 ; if nothing follows
If i ; if not the first byte of the OID
OID$ + "." + Str(ID)
Else ; first byte is decoded different
OID$ = "." + Str(byte / 40) + "." + Str(byte % 40)
EndIf
ID = 0
EndIf
i + 1
Wend
ProcedureReturn OID$
EndProcedure
Procedure.l BM_Integer(value.q, *memory, *pos.Reference)
; Writes integer value into memory, starting at position *pos
; Returns number of used Bytes...
Protected IntSize.i, Bytes.i, Mask.q
IntSize = 4
Mask = $FF800000
While ((value & Mask = 0) Or (value & Mask = Mask)) And IntSize > 1
value << 8
IntSize - 1
Wend
Mask = $FF000000
Bytes = 0
While IntSize <> 0
PokeA(*memory + *pos\value + Bytes, (value & Mask) >> 24)
value << 8
IntSize - 1
Bytes + 1
Wend
*pos\value + Bytes
ProcedureReturn Bytes
EndProcedure
Procedure.i BM_Value(value.i, *memory, *pos.Reference)
; should use quads, but because of pures poor quad handling limited to long for now ;(
; Writes integer value into memory, starting at position *pos
; Returns number of used Bytes...
If value >= 0 And value < 128
PokeA(*memory + *pos\Value, value)
value = 1
ElseIf value >= 128 And value < 16384
; Debug Str(value)+" = "+Str($80|(value>>7))+", "+Str(value&$7f)
PokeA(*memory+ *pos\Value, $80 | (value >> 7))
PokeA(*memory+ *pos\Value + 1, value & $7f)
value = 2
ElseIf value >= 16384 And value < 2097152
PokeA(*memory + *pos\Value, $80 | ((value >> 14) & $7f))
PokeA(*memory + *pos\Value + 1, $80 | ((value >> 7) & $7f))
PokeA(*memory + *pos\Value + 2, value & $7f)
value = 3
ElseIf value >= 2097152 And value < 268435456
PokeA(*memory + *pos\Value, $80 | ((value >> 21) & $7f))
PokeA(*memory + *pos\Value + 1, $80 | ((value >> 14) & $7f))
PokeA(*memory + *pos\Value + 2, $80 | ((value >> 7) & $7f))
PokeA(*memory + *pos\Value + 3, value & $7f)
value = 4
ElseIf value >= 268435456 And value < 4294967296
PokeA(*memory + *pos\Value, $80 | ((value >> 28) & $7f))
PokeA(*memory + *pos\Value + 1, $80 | ((value >> 21) & $7f))
PokeA(*memory + *pos\Value + 2, $80 | ((value >> 14) & $7f))
PokeA(*memory + *pos\Value + 3, $80 | ((value >> 7) & $7f))
PokeA(*memory + *pos\Value + 4, value & $7f)
value = 5
Else
value = 0
EndIf
*pos\Value + value
ProcedureReturn value
EndProcedure
Procedure.i BM_String(value$, *memory, *pos.Reference)
; Writes string value into memory, starting at position *pos
; Returns number of used Bytes...
Protected length.i
length = Len(value$)
If length
PokeS(*memory + *pos\Value, value$, length, #PB_Ascii)
*pos\Value + length
EndIf
ProcedureReturn length
EndProcedure
Procedure.i BM_ObjectIDentifier(OID.s, *memory, *pos.Reference)
; Writes OID object into memory starting at position *pos
; Returns number of used bytes
Protected value.i, i.i, n.i, length.i
OID = LTrim(OID, ".")
n = CountString(OID, ".")
value = Val(StringField(OID, 1, ".")) * 40 + Val(StringField(OID, 2, "."))
length + BM_Value(value, *memory, *pos)
If CountString(OID, ".") > 1
For i = 3 To n + 1
length + BM_Value(Val(StringField(OID, i, ".")), *memory, *pos)
Next
EndIf
ProcedureReturn length
EndProcedure
Procedure.i BM_Move(value.i, length.i, *memory, *pos.Reference)
; Insert byte value into memory block...
; Returns total length
MoveMemory(*memory + *pos\Value - length, *memory + *pos\Value - length + 2, length)
PokeA(*memory + *pos\Value - length, value)
PokeA(*memory + *pos\Value - length + 1, length)
*pos\Value + 2
ProcedureReturn length + 2
EndProcedure
Procedure.i BM_Null(value.i, *memory, *pos.Reference)
; Writes byte value into memory address...
; Returns length (=2)
PokeA(*memory + *pos\Value, value)
PokeA(*memory + *pos\Value + 1, 0)
*pos\Value + 2
ProcedureReturn 2
EndProcedure
CompilerIf #PB_Compiler_OS <> #PB_OS_Linux
Procedure.l Ping(n)
; Pings Station(n)\IP...
; Returns #True if a reply packet is seen from the station
Protected EchoMessage.s
Protected EchoSize.l
Protected *Echo.ICMP_ECHO_REPLY
Protected *EchoResult
Protected Handle
SelectElement(SNMPStation(), n)
With SNMPStation()
\Alive = #Station_Down
EchoMessage.s = "Ping"
EchoSize = SizeOf(ICMP_ECHO_REPLY) + Len(EchoMessage)
*EchoResult = AllocateMemory(EchoSize)
*Echo = *EchoResult
If \IP
Handle = IcmpCreateFile_()
If IcmpSendEcho_(Handle, \IP,EchoMessage, Len(EchoMessage), 0, *EchoResult, EchoSize, 500)
; Received an ICMP-Response...
; If PeekL(*EchoResult)=\IP
;...from the destination address (otherwise it will be a destination unreachable message from the default gateway)
\Alive = #Station_Up
; EndIf
EndIf
IcmpCloseHandle_(Handle)
EndIf
FreeMemory(*EchoResult)
ProcedureReturn \Alive
EndWith
EndProcedure
CompilerEndIf
Procedure.l PDU_Check(n, *memory)
Protected.a byte
Protected.i length, pos, field
Protected Value$
; Class (Bits 7/8)
; 0 - Universal (integer, string etc.)
; 1 - Application (IP address, Time Ticks)
; 2 - Context (complex data)
; 3 - Private (non standard data)
; Data Type (Bit 6)
; 0 - primitive data-type
; 1 - constructed data-type
; ASN.1 Type (Bit 5)
; 0 - no
; 1 - yes
SelectElement(SNMP(), n)
With SNMP()
pos = 0
Repeat
byte = PeekA(*memory + pos)
;Debug "Field: "+Str(field)+" Pos: "+Str(pos)+" (=$"+Hex(byte&$ff)+")"
pos + 1
length = BER_Length(*memory, @pos);
Select byte & $C0 ; mask the class...
; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Case #ClassUniversal ; universal class
; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If byte & #TypeConstructed ; constructed data type
; Debug "Constructed"
If byte & $10 ; ASN.1
;Debug " Length: "+Str(length)
EndIf
Else; primitive data type
Select byte & $1F
Case #TypeInteger
\ValueType = #TypeInteger
Value$ = Str(BER_Integer(*memory + pos, length))
Debug " integer: " + Value$
Case #TypeBitString
Debug " bit string"
Case #TypeOctetString
\ValueType = #TypeOctetString
Value$ = PeekS(*memory+pos, length, #PB_Ascii)
Debug " octet string: " + Value$
Case #TypeNull
Debug " null"
Case #TypeObjectIdentifier
\ValueType = #TypeObjectIdentifier
Value$ = BER_ObjectIDentifier(*memory + pos, length)
Debug " object: " + Value$
Default
Debug Str(n) + " UNKNOWN TYPE: " + Str(byte)
EndSelect
pos + length
EndIf
; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Case #ClassApplication ; application
; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If byte & #TypeConstructed = 0; primitive data type
Select byte & $1F
Case #TypeNetworkAddress; IPAddress
Value$ = ""
Protected i
For i = 0 To length - 1
Value$ + Str(PeekA(*memory + pos + i) )
If i < length - 1 : Value$ + "." : EndIf
Next i
\ValueType = #TypeNetworkAddress
Case #TypeCounter; Counter
Value$ = Str(BER_Integer(*memory + pos, length))
\ValueType = #TypeCounter
Case #TypeGauge; Gauge
Value$ = Str(BER_Integer(*memory + pos, length))
\ValueType = #TypeGauge
Case #TypeTimeTicks; ticks
Value$ = Str(BER_Integer(*memory + pos, length))
\ValueType = #TypeTimeTicks
Case #TypeOpaque; Opaque
Value$ = PeekS(*memory + pos, length)
\ValueType = #TypeOpaque
; Added by Hujambo
Case #TypeCounter64; Counter 64 bit
Value$ = Str(BER_Integer(*memory + pos, length))
\ValueType = #TypeCounter64
EndSelect
EndIf
; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Case #ClassContextSpecific ; context specific
; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If byte & #TypeConstructed; constructed data type
Select byte & $1F
Case #PDU_RESPONSE
Debug "GetResponse PDU..."
Case #PDU_TRAP
Debug "Trap PDU..."
EndSelect
Else; primitive data type
Debug Str(n) + " ERROR: Unknown Packet"
\Status = #SNMP_Error
field = #Field_Nil
EndIf
EndSelect
; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Select field
Case #Field_ErrorState
If Val(Value$)
\Status = #SNMP_Error_Name
Debug \Status
Debug Str(n) + " NAME ERROR: " + Value$
field=#Field_Nil
EndIf
Case #Field_RequestID
If Val(Value$) <> \RequestID
\Status = #SNMP_Error_RID
Debug Str(n) + " RID ERROR: " + Value$ + "~" + \RequestID
field = #Field_Nil
EndIf
Case #Field_OID
\ResponseOID = Value$
If \PDUType = #PDU_GET And Value$ <> \OID
\Status = #SNMP_Error_OID
Debug Str(n) + " OID ERROR: " + Value$ + "~" + \OID
field = #Field_Nil
EndIf
Case #Field_Variable
\Value = Value$
\Status = #SNMP_Ok
EndSelect
field + 1
Until field > #Field_Variable
ProcedureReturn \Status
EndWith
EndProcedure
Procedure.l PDU_Make(n, *memory)
; compose SNMP packet into reserved memory (548 bytes)
; n = Request number
; Returns packet length
Protected length = 0
Protected pos = 0
Protected PositionA, PositionB
Static RequestID.l = 0
SelectElement(SNMP(), n)
With SNMP()
SelectElement(SNMPStation(), \StationNumber)
RequestID + 1
If RequestID = 65536 : RequestID = 0 : EndIf ; toavoid trouble wit negative values
\RequestID = RequestID
; If \GetType <> #GET_NEXT : \GetType = #GET : EndIf
; hier muss später die Gesamtlänge rein
length = BM_Integer(SNMPStation()\SNMPVersion, *memory, @pos)
length = BM_Move(#TypeInteger, length, *memory, @pos)
; Community
If \PDUType = #PDU_GET Or \PDUType = #PDU_GET_NEXT
length = BM_String(SNMPStation()\ReadCommunity, *memory, @pos)
Else
length = BM_String(SNMPStation()\WriteCommunity, *memory, @pos)
EndIf
length = BM_Move(#TypeOctetString, length, *memory, @pos)
PositionA = pos ; hier muss später der Requesttype und seine Länge rein
; Request ID
length = BM_Integer(\RequestID, *memory, @pos)
length = BM_Move(#TypeInteger, length, *memory, @pos)
; Error-Status
length = BM_Value(#ERRORSTATUS_NOERROR, *memory, @pos)
length = BM_Move(#TypeInteger, length, *memory, @pos)
; Error-Index
length = BM_Value(0, *memory, @pos)
length = BM_Move(#TypeInteger, length, *memory, @pos)
PositionB = pos ; hier muss später die Varbindlistlänge rein
; hier muss später die Länge des ersten varbinds rein
; OID
length = BM_ObjectIDentifier(\OID, *memory, @pos)
\OID = BER_ObjectIDentifier(*memory + PositionB, length); put normalized OID into database
length = BM_Move(#TypeObjectIdentifier, length, *memory, @pos)
If \PDUType = #PDU_GET Or \PDUType = #PDU_GET_NEXT
; NULL
length = BM_Null(#TypeNull, *memory, @pos)
Else
; put the value for a set request
Select \ValueType
Case #TypeInteger
length = BM_Integer(Val(\Value), *memory, @pos)
length = BM_Move(#TypeInteger, length, *memory, @pos)
Case #TypeOctetString
length = BM_String(\Value, *memory, @pos)
length = BM_Move(#TypeOctetString, length, *memory, @pos)
EndSelect
EndIf
; Envelope OID and Null-field (two times)
length = BM_Move(#ClassUniversal|#TypeConstructed|#TypeSequence, pos - PositionB, *memory, @pos) ; trage Länge Varbind ein
length = BM_Move(#ClassUniversal|#TypeConstructed|#TypeSequence, pos - PositionB, *memory, @pos) ; trage Länge Varbindlist ein
; Envelope Request-ID, Error Information and OID-Envelope
length = BM_Move(#ClassContextSpecific|#TypeConstructed|\PDUType, pos - PositionA, *memory, @pos) ; trage Typ und Länge der PDU ein
; Envelope all SNMP fields
length = BM_Move(#ClassUniversal|#TypeConstructed|#TypeSequence, pos, *memory, @pos) ; trage Gesamtlänge ein
EndWith
ProcedureReturn pos
EndProcedure
Procedure.i SNMPRequest(n.i)
Protected *Buffer, Status.i, Start.i, Client.i
SelectElement(SNMP(), n)
With SNMP()
SelectElement(SNMPStation(), \StationNumber)
\Status = #SNMP_Unknown
Status = 0
If SNMPStation()\IP
Client = OpenNetworkConnection(SNMPStation()\IP, 161 , #PB_Network_UDP)
If Client
*Buffer = AllocateMemory(#SNMP_MaxPacketSize)
If *Buffer
Status = PDU_Make(n, *Buffer)
If Status
If SendNetworkData(Client, *Buffer, Status) = Status
Start = Date()
Repeat
Delay(1)
Status = NetworkClientEvent(Client)
If Date() - Start > 5
Debug "Timeout"
Status = #SOCKET_ERROR ; = -1 Timeout
EndIf
Until Status <> 0
If Status = #PB_NetworkEvent_Data ; 2
If ReceiveNetworkData(Client, *Buffer, #SNMP_MaxPacketSize) > 0
\Status = PDU_Check(n, *Buffer)
If \Status = #SNMP_Ok : Status = 1
Else : Status = 0 : EndIf
Else
Status = 0
\Status = -99 ; ReceiveLengthError
EndIf
Else
Status = 0
\Status = -98 ; ReceiveError #SNMP_Error
EndIf
EndIf
EndIf
FreeMemory(*Buffer)
EndIf
CloseNetworkConnection(Client)
EndIf
EndIf
EndWith
ProcedureReturn Status
EndProcedure
CompilerIf #PB_Compiler_IsMainFile
If InitNetwork()
AddElement(SNMPStation())
SNMPStation()\IP = "192.168.0.1"
SNMPStation()\ReadCommunity = "public"
SNMPStation()\WriteCommunity = "private"
SNMPStation()\SNMPVersion = #SNMPv1
AddElement(SNMP())
SNMP()\StationNumber = 0
SNMP()\OID ="1.3.6.1.4.1.1909.32.3.1.1.3.4"
SNMP()\PDUType = #PDU_GET
SNMP()\Value = ""
SNMP()\ValueType = #TypeUnknown
SNMP()\Status = #SNMP_Unknown
SNMPRequest(0)
If SNMP()\Status = #SNMP_Error
MessageRequester("Results", "Error")
Else
MessageRequester("Results", "Status: " + Str(SNMP()\Status) + #LF$ + "Type: " + Str(SNMP()\ValueType) + #LF$ + "Value: " + SNMP()\Value)
EndIf
EndIf
CompilerEndIf