SNMP

A simple SNMP agent

There will be many agents to each NMS and a very very wide variety of implementations, some based upon 8 bit embedded TinyIP others full blown Linux S.O multi-task. The section on the OID has given information for thoughts of a simple Linux agents, so here we will look at sending traps.

The Trap

Traps are an asynchronous communication from the agent to a NMS (or various NMS). Asynchronous because the information is sent when the agent decided rather than on demand from the NMS. Traps are the result of events eg; "the fan on amplifier 4 has failed" or "Power failure, shut down in 1 minute".

Events can be routine eg: "I am still here", "Radio 4 program has changed to source 3" or alarms eg:"The reflected power form aerial 3 is excessive", for that reason it is frequent that a system of priority is used so that the NMS can sort the traps and take the appropiate action.

#define TRAPCOMMUNITYLENGTH 16
//								  p     u     b     l     i     c     null  null......
static char sSNMPtrapcommunity[TRAPCOMMUNITYLENGTH] = {0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
static const char* ptr_sSNMPtrapcommunity = (char*)sSNMPtrapcommunity;
.......
const char sPSUMessage[] = { " <Power supply alarm> " };
.......
static char sTrapMessage[80];
static char* ptr_sTrapMessage = sTrapMessage;
.......
static const	uint8_t stop_led_trap[] 	= {0x2b,6,1,4,1,0x82,0x44,0x4F,1,1,1,3,2,STOP_LED_TRAP,0};
static const	uint8_t heartbeat_trap[] 	= {0x2b,6,1,4,1,0x82,0x44,0x4F,1,1,1,3,3,2,0};
.......
#define SIZEOFTRAPBUFFER 384
static void SendSnmpTrap(int trap_number)
{
	//	Send a trap. Eg: when there is an alarm
	bool bGenerateEvent = true; //	By default
	//	The trap should coincide with the corresponding define
    uint8_t * pBuf;
    uint8_t * pEnd;
    uint8_t * pPrev;
    uint8_t * pCur;
    uint8_t * pTrapOID;
    uint8_t count = 0;
    uint16_t trapOID_size = 0;
    uint32_t network = 0;
    char tmp[64];
    //	There is an alarm_sequence_number that we increment for every trap sent up to 8092 limit
    //	The trap number is offset by the BASE_TRAP value
    int trap = (trap_number - BASE_TRAP);
    // Allocate a varbind buffer and ensure enough space...
    pBuf = MALLOC(SIZEOFTRAPBUFFER);
    // Write the varbind starting from the end
    pEnd = pCur = (uint8_t *)((uint8_t HUGE *)pBuf + SIZEOFTRAPBUFFER);
    // 	Encode the trap
    //	A configurable severity level is to be encoded on each trap alarm_severity_clasification[]
    //	The traps are encoded with the following fields:
    //	<Sequence Number><OID with value><Severity><Time Stamp>
    //	All traps have a common date_time field so we generate this first of all
    sTrapMessage[0] = 0x00;	// null
    strcat(sTrapMessage, GetDateTimeString(tmp));
    //	These traps are converted to events and saved. Except for the heartbeat
    switch (trap)
    {
    	case HEARTBEAT_TRAP:
    		bGenerateEvent = false;
            //	Encode sName and heartbeat message
            strcat(sTrapMessage, GetSequenceNumber(tmp));
            strcat(sTrapMessage, GetBracketedName(tmp));
            strcat(sTrapMessage, sHeartbeatMessage);
            strcat(sTrapMessage, GetSeverity(256, tmp));
    	    berRWriteValue((uint8_t HUGE **)&pCur, (const uint8_t HUGE *)sTrapMessage, SNMP_String, strlen((char*)sTrapMessage));
    	    // Encode the heartbeat OID, the index is for heartbeat OID
    	    berRWriteValue((uint8_t HUGE **)&pCur,
    	        (const uint8_t HUGE *)mibVar[HEARTBEAT_MIB_INDEX].oid.name,
    	        SNMP_Identifier, mibVar[HEARTBEAT_MIB_INDEX].oid.nlen);
    	    //	Encode the sequence
    	    berRWriteLength((uint8_t HUGE **)&pCur, SNMP_Sequence, (int)((uint8_t HUGE *)pEnd - (uint8_t HUGE *)pCur));
    	    // Send the trap
     	    trapOID_size = sizeof(heartbeat_trap);
    	    SnmpTrap((const uint8_t*)sSNMPtrapcommunity, heartbeat_trap, sizeof(heartbeat_trap), (const uint8_t *)pCur,
    	        (uint16_t)((uint8_t HUGE *)pEnd - (uint8_t HUGE *)pCur));
    	    pTrapOID = MALLOC(trapOID_size);
    	    //	Copy OID into the newly reserved space
    	    for(count = 0; count < trapOID_size; count++) *(pTrapOID + count) = heartbeat_trap[count];
    		break;
   	case STOP_LED_TRAP:		//	bit	17
    		//	Encode the integer value
    	    berRWriteInt((uint8_t HUGE **)&pCur, Stop_Led, SNMP_Integer, sizeof(Stop_Led));
    	    // Encode the Stop_Led OID, the index is for the stop_led OID
    	    berRWriteValue((uint8_t HUGE **)&pCur,
    	        (const uint8_t HUGE *)mibVar[STOP_LED_MIB_INDEX].oid.name,
    	        SNMP_Identifier, mibVar[STOP_LED_MIB_INDEX].oid.nlen);
    	    //	Encode the sequence
    	    berRWriteLength((uint8_t HUGE **)&pCur, SNMP_Sequence, (int)((uint8_t HUGE *)pEnd - (uint8_t HUGE *)pCur));
            // Save pointer to end for next varbind
            pPrev = pCur;
           //	Encode sName and stop message
            //	Get the latest alarm_sequence_number as a string
            strcat(sTrapMessage, GetSequenceNumber(tmp));
            strcat(sTrapMessage, GetBracketedName(tmp));
            strcat(sTrapMessage, sStopMessage);
            strcat(sTrapMessage, GetSeverity(17, tmp));
    	    berRWriteValue((uint8_t HUGE **)&pCur, (const uint8_t HUGE *)sTrapMessage, SNMP_String, strlen((char*)sTrapMessage));
    	    // Encode the Stop OID, the index is for stop OID
    	    berRWriteValue((uint8_t HUGE **)&pCur,
    	        (const uint8_t HUGE *)mibVar[TRAP_MESSAGE_MIB_INDEX].oid.name,
    	        SNMP_Identifier, mibVar[TRAP_MESSAGE_MIB_INDEX].oid.nlen);
    	    //	Encode the sequence
    	    berRWriteLength((uint8_t HUGE **)&pCur, SNMP_Sequence, (int)((uint8_t HUGE *)pPrev - (uint8_t HUGE *)pCur));
    	    // Send the trap
     	    trapOID_size = sizeof(stop_led_trap);
    	    SnmpTrap((const uint8_t*)sSNMPtrapcommunity, stop_led_trap, sizeof(stop_led_trap), (const uint8_t *)pCur,
    	        (uint16_t)((uint8_t HUGE *)pEnd - (uint8_t HUGE *)pCur));
    	    pTrapOID = MALLOC(trapOID_size);
    	    //	Copy OID into the newly reserved space
    	    for(count = 0; count < trapOID_size; count++) *(pTrapOID + count) = stop_led_trap[count];
    		break;
    	default:
    		break;
    }
    //	Free up the buffer
    Free(pBuf);
    network = NetDottedTo32BitNetworkByteOrder((char*)sSNMPaddress2);
    if(network != 0) CallSendTrapTo((const uint8_t *)sSNMPtrapcommunity, (const uint8_t *)sSNMPaddress2, (const uint8_t *)pTrapOID, trapOID_size, (uint8_t *)pCur,
	        (const uint8_t *)pEnd);
    network = NetDottedTo32BitNetworkByteOrder((char*)sSNMPaddress3);
    if(network != 0) CallSendTrapTo((const uint8_t *)sSNMPtrapcommunity, (const uint8_t *)sSNMPaddress3, (const uint8_t *)pTrapOID, trapOID_size, (uint8_t *)pCur,
    		(const uint8_t *)pEnd);
    //	Free up the buffers
    Free(pBuf);
    Free(pTrapOID);

First we define the OID for the trap community. then a list of standard messages is required for all the standard traps (ie; alarm associated). Space is reserved for the combined trap message (here limited to 80 chars). Next all the OID's for the possible trap messages have to be organised in a tree within the trap community group (will be a long list).

Finally there is the function to generate the traps in a buffer pBuf[sup to 384], a large switch. Entry is with the trap number of the trap to be generated. Two examples are given for the previously given trap OID. The information sequence is "Sequence Number OID with value Severity Time Stamp". having prepared the trap ANS.1 encoded payload the function CallSendTrapTo() is used to send the same trap to various trap destinations.

static void CallSendTrapTo(const uint8_t *contextName, const uint8_t *trapaddress, const uint8_t *oid, uint16_t oidLen, uint8_t *pCur, const uint8_t *pEnd)
{
	//	Send our own trap somewhere
	//	contextName	The community string, normally public terminated in null
	//	trapaddress	The IP address in dotted string format terminated in null
	//	oid			The oid of length oidLen
	//	vbs			The ANS.1 coded payload of length vbsLen
	//	Basically we take the predefined SNMPv2c structure and plug in the information we have.
	//	From then on it is standard Berkely socket stuff
	struct sockaddr_in	updSerAddr;
	int udpSrvSocket = -1;
	uint32_t	uptime_sec = 0;
	uint32_t	uptime_msec = 0;
	uint32_t	ticks = 0;
    uint8_t * pPrev = 0x00;
    uint32_t	seq_num = 32000;
	uint16_t vbsLen = (uint16_t)((uint8_t HUGE *)pEnd - (uint8_t HUGE *)pCur);
	//	The datagram is constructed going back from *vbs, The maximum length vbsLen < SIZEOFTRAPBUFFER
	if(vbsLen > 0xC0) return;
	pPrev = pCur;
	//	Add on the OID
    berRWriteValue((uint8_t HUGE **)&pCur, (const uint8_t HUGE *)oid, SNMP_Identifier, oidLen);
	//	Add on snmpTrapOID 1.3.6.1.6.3.1.1.4.1.0
    pCur--;	//	To overwrite the size value that was inserted by berRWriteValue
    *(pCur--) 	= 0x00;
    *(pCur--)	= 0x01;
    *(pCur--) 	= 0x04;
    *(pCur--) 	= 0x01;
    *(pCur--) 	= 0x01;
    *(pCur--) 	= 0x03;
    *(pCur--) 	= 0x06;
    *(pCur--) 	= 0x01;
    *(pCur--) 	= 0x06;
    *(pCur--) 	= 0x2B;
    *(pCur--) 	= 0x0A;
    *(pCur--) 	= 0x06;
	vbsLen = (uint16_t)((uint8_t HUGE *)pPrev - (uint8_t HUGE *)pCur) - 1;
    *(pCur--)	= (uint8_t)vbsLen;	//	< 0xFF
    *(pCur)	= 0x30;		//	Start to tick OID
    //	Add on uptime
    uptime_sec = UpTimeSeconds(&uptime_msec);
    ticks = (uptime_sec * 1000 + uptime_msec) / 10;	//	ticks of 10mSec
    pPrev = pCur;
    berRWriteInt((uint8_t HUGE **)&pCur, ticks, ASN1_UINTEGER32, sizeof(ticks));
    *(pCur--) 	= 0x43;		//	Start to ticks value
    //	The time ticks OID sysUpTime 1.3.6.1.2.1.1.3.0
    *(pCur--) 	= 0x00;
    *(pCur--) 	= 0x03;
    *(pCur--) 	= 0x01;
    *(pCur--) 	= 0x01;
    *(pCur--) 	= 0x02;
    *(pCur--) 	= 0x01;
    *(pCur--) 	= 0x06;
    *(pCur--) 	= 0x2B;
    *(pCur--) 	= 0x08;
    *(pCur--) 	= 0x06;
	vbsLen = (uint16_t)((uint8_t HUGE *)pPrev - (uint8_t HUGE *)pCur) -1;
    *(pCur--)	= (uint8_t)vbsLen;	//	< 0xFF
    *(pCur--)	= 0x30;		//	Start to tick OID
    //	The length of all the bindings
	vbsLen = (uint16_t)((uint8_t HUGE *)pEnd - (uint8_t HUGE *)pCur) - 1;
    *(pCur--) 	= (uint8_t)vbsLen;	//	< 0xFF
    *(pCur--)	=	0x81;	//	Multiple sequences
    *(pCur--)	=	0x30;	//	Start to bindings sequence
	//	For traps error index is 0
	*(pCur--) 	= 0x00;		//	The value
	*(pCur--) 	= 0x01;		//	Value length 1
	*(pCur--) 	= 0x02;		//	Value type integer
	//	For traps error status is no error = 0
	*(pCur--) 	= 0x00;		//	The value
	*(pCur--) 	= 0x01;		//	Value length 1
	*(pCur) 	= 0x02;		//	Value type integer
	//	Add on a request id
	//	Since this is a trap not a notification any old identification can be used but in our case since we have a sequence number
	//	we use that sequence_number 0 - 8192 + 32000
	seq_num += alarm_sequence_number;
	berRWriteInt((uint8_t HUGE **)&pCur, seq_num, SNMP_Integer, sizeof(seq_num));
    *(pCur--)	= 0x02;		//	sequence number to follow
    //	Add on trap header. Max length 256 byte
	vbsLen = (uint16_t)((uint8_t HUGE *)pEnd - (uint8_t HUGE *)pCur) - 1;
    *(pCur--) 	= (uint8_t)vbsLen;
    *(pCur--) 	= 0x81;		//	1 byte with a variable context length
    *(pCur) 	= 0xA7;		//	Universal constructed object-identifier ie: a trap
    //	END OF PDU
    //	Now add on the variable length community in sSNMPtrapcommunity
    berRWriteValue((uint8_t HUGE **)&pCur, (const uint8_t HUGE *)contextName, SNMP_String, strlen((char*)contextName));
    *(pCur--)	= 0x04;		//	To define the following string type
    //	Add on SNMPv2c
	*(pCur--) 	= 0x01;		//	The value for SNMPv2c
	*(pCur--) 	= 0x01;		//	Value length 1
	*(pCur--) 	= 0x02;		//	Value type integer
	//	Add on total of the UDP payload < 0xFF
	vbsLen = (uint16_t)((uint8_t HUGE *)pEnd - (uint8_t HUGE *)pCur) - 1;
	if(vbsLen > 0xFE) vbsLen = 0xFE;
	*(pCur--)	= (uint8_t)vbsLen;
	*(pCur--) 	= 0x81;		//	Multiple sequence
	*(pCur) 	= 0x30;		//	To ensure pCur now points to first position of payload
	//	We have the complete payload now so send into the socket
	//	Setup the socket information
	memset(&updSerAddr, 0, sizeof(updSerAddr));
	updSerAddr.sin_family 		= AF_INET;
	updSerAddr.sin_port			= htons(162);
	updSerAddr.sin_addr.s_addr	= NetDottedTo32BitNetworkByteOrder((const char*)trapaddress);
	udpSrvSocket = socket(PF_INET, SOCK_DGRAM, 0);
	if(udpSrvSocket >= 0)
	{
		//	Socket initialised
		(void)sendto(udpSrvSocket, (const char*)pCur, (uint16_t)((uint8_t HUGE *)pEnd - (uint8_t HUGE *)pCur),
				0, (struct sockaddr *)&updSerAddr, sizeof(updSerAddr));
	}
	closesocket(udpSrvSocket);	//	Done with socket
}

The sending is done by filling in a pre-defined SNMPv2c frame with the payload information, then sending the frame as a UPD datagram to the required IP address.