emNet SNMP Agent (ZeroCopy) (Sample)
Jump to navigation
Jump to search
| IP_SNMP_Agent_Start_ZeroCopy_Sample.c | |
|---|---|
| Requires modifications | Yes |
| Download | IP_SNMP_Agent_Start_ZeroCopy_Sample.c |
This Sample demonstrates the use of emNet together with the SNMP Agent add-on and the zero-copy interface. The SNMP Agent can be tested with the Net-SNMP toolset or with any other SNMP Manager. The Sample requires BSP_Init() for LED routines.
A sample MIB has been added to the following OID, being able to control 8 LEDs: iso(1).org(3).dod(6).internet(1).private(4).enterprise(1).PRIVATE_ENTERPRISE_NUMBER(46410).
The following indexes are available for the sample MIB:
.0 : (get-request only) Get the status of all 8 LEDs in one byte.
Bit 0 set means LED0 is lit.
.1-8: Get/Set the status of one LED. .1 indexes LED1. Writing 0 as
value clears the LED, any other value sets the LED.
On Linux (or on Windows using WSL) you can use the following commands to test the sample:
This will set LED 1 to ON: snmpset -v2c -c Segger <IP> .1.3.6.1.4.1.46410.1 i 1
This will set LED 2 to ON: snmpset -v2c -c Segger <IP> .1.3.6.1.4.1.46410.2 i 1
This will retrieve the bitmap of the LEDs states: snmpget -v2c -c Segger <IP> .1.3.6.1.4.1.46410.0
This will turn off LED 1: snmpset -v2c -c Segger <IP> .1.3.6.1.4.1.46410.1 i 0
In the emNet shipments, another SNMP sample is included that automatically enables SNMPv3 for the Sample. Though this can also be set in the application itself.
Code
/*********************************************************************
* (c) SEGGER Microcontroller GmbH *
* The Embedded Experts *
* www.segger.com *
**********************************************************************
-------------------------- END-OF-HEADER -----------------------------
Purpose : Sample program for embOS & TCP/IP & SNMP Agent & zero-copy API.
Notes:
(1) The following requirements need to be met when including this
sample into another C-module:
- BSP_Init() for LED routines and IP_Init() for emNet are
called before starting APP_MainTask() .
- The optional IP_Connect() is called and a link state hook
is installed if necessary for the emNet interface used
before starting APP_MainTask() .
(2) The following application defines can be overwritten when
including this sample into another C-module:
- APP_MAIN_STACK_SIZE
Stack size in bytes to use for APP_MainTask() .
- APP_ENABLE_SNMPV3_USM
Enables/disables SNMPv3 USM support.
(3) The following symbols can be used and renamed via preprocessor
if necessary when including this sample into another C-module:
- MainTask
Main starting point of the sample when used as a
standalone sample. Can be renamed to anything else to
avoid multiple MainTask symbols and to skip common
initializing steps done in every sample.
- APP_MainTask
Functional starting point of the sample itself. Typically
called by the MainTask in this sample. Should be renamed
via preprocessor to a more application specific name to
avoid having multiple APP_MainTask symbols in linker map
files for a better overview when including multiple samples
this way.
- APP_MainTCB
Task-Control-Block used for the APP_MainTask when started
as a task from the MainTask in this sample. Can/should be
renamed via preprocessor to a more application specific name.
- APP_MainStack
Task stack used for the APP_MainTask when started as a task
from the MainTask in this sample. Can/should be renamed via
preprocessor to a more application specific name.
Additional information:
For compatibility with interfaces that need to connect in any way this
sample calls connect and disconnect routines that might not be needed
in all cases.
This sample can be used for Ethernet and dial-up interfaces and is
configured to use the last registered interface as its main interface.
This sample demonstrates use of the IP stack together with the SNMP
Agent add-on. The SNMP Agent can be tested with the Net-SNMP toolset
or with any other SNMP Manager. A sample MIB has been added to the
following OID, being able to control 8 LEDs:
iso(1).org(3).dod(6).internet(1).private(4).enterprise(1).PRIVATE_ENTERPRISE_NUMBER .
The following indexes Are available for the sample MIB:
.0 : (get-request only) Get the status of all 8 LEDs in one byte.
Bit 0 set means LED0 is lit.
.1-8: Get/Set the status of one LED. .1 indexes LED1. Writing 0 as
value clears the LED, any other value sets the LED.
Boot traps are sent to the receivers that are configured in the
section "Configuration, TRAP/INFORM receivers".
Optional SNMPv3 support can be enabled via the APP_ENABLE_SNMPV3_USM
define.In addition to the basic SNMP Agent this also requires the
optional SNMPv3 MessageProcessorV3 (MPV3), User-basedSecurityModel (USM)
and some hash and encrypt/decrypt modules provided with the respective
SNMPv3 add-ons for the Agent.
*/
#include "RTOS.h"
#include "BSP.h"
#include "IP.h"
#include "TaskPrio.h"
#include "IP_SNMP_AGENT.h"
#include "SEGGER_UTIL.h"
/*********************************************************************
*
* Defines, configurable
*
**********************************************************************
*/
#ifndef SNMP_MEMCPY
#define SNMP_MEMCPY memcpy
#endif
#ifndef SNMP_MEMSET
#define SNMP_MEMSET memset
#endif
#ifndef SNMP_STRLEN
#define SNMP_STRLEN strlen
#endif
#ifndef USE_RX_TASK
#define USE_RX_TASK 0 // 0: Packets are read in ISR, 1: Packets are read in a task of its own.
#endif
//
// Sample features enable/disable
//
#ifndef APP_ENABLE_SNMPV3_USM
#define APP_ENABLE_SNMPV3_USM (0) // 0: Only SNMPv1/SNMPv2 messages are supported, 1: SNMPv1/SNMPv2/SNMPv3 messages are supported.
#endif
//
// Number of potential SNMP TRAP/INFORM contexts that can be used
// in this sample. The actual receivers and number of receivers
// is configured in the "Configuration, TRAP/INFORM receivers"
// section.
//
// For SNMPv3 that many placeholder entries for SNMP Engines will
// be created. How many of them are used depends on the SNMPv3
// INFORM receivers configured in the "Configuration, TRAP/INFORM receivers"
// section.
//
#define MAX_TRAP_INFORM_RECEIVERS 5u
#if APP_ENABLE_SNMPV3_USM
#include "CRYPTO.h"
#define ENGINE_AUTH_TIMEOUT 150u // The time window [s] in which an SNMP message with AUTH(entication) information is considered valid (NOW +- TIMEOUT).
//
// All users in this sample use the same password for AUTH(entication) and PRIV(acy).
//
#define AUTH_PASSWORD "AUTHpass" // Minimum 8 characters long. The sample uses the same AUTH password for all users.
#define PRIV_PASSWORD "PRIVpass" // Minimum 8 characters long. The sample uses the same PRIV password for all users.
//
// Max. entries in SNMPv3 USM userlist.
// The userlist contains entries for Users for the local SNMP Engine (when receiving requests or sending TRAPs)
// as well as placeholder entries for INFORM messages for which we have to discover the peer EngineId or the
// EngineBoots and EngineTime values for the peer Engine.
//
#define MAX_USERS (7u + MAX_TRAP_INFORM_RECEIVERS)
#endif
//
// The Private Enterprise Number used by default in this sample is the PEN of SEGEGR Microcontroller GmbH & Co. KG.
// Your own PEN can be acquired from the IANA free of charge. It is not allowed to use the SEGGER PEN in your own product.
//
#define PRIVATE_ENTERPRISE_NUMBER 46410
//
// SNMP Agent configuration.
//
#ifndef MESSAGE_PORT
#define MESSAGE_PORT 161
#endif
#ifndef TRAP_INFORM_PORT
#define TRAP_INFORM_PORT 162
#endif
//
// Typically Engine discover messages (empty get-request) to request
// the Agent EngineId as well as EngineBoots and EngineTime value
// is sent on the message port (default 161).
// For TRAP/INFORM test tools like "snmptrapd" however, the tool
// opens only the TRAP/INFORM port (default 162) to receive messages.
// Typically SNMP participants do not really care about the port on
// which a messages is received as all messages are hanlded by the
// same message dispatcher. As the TRAP/INFORM port should be open
// anyhow when sending a TRAP or INFORM message, our default is to
// use the TRAP/INFORM port over the message port as default.
//
#ifndef ENGINE_DISCOVER_PORT
#define ENGINE_DISCOVER_PORT TRAP_INFORM_PORT
#endif
#ifndef MAX_SNMP_MESSAGE_SIZE
#define MAX_SNMP_MESSAGE_SIZE 1472 // MTU(1500) - IPHeader(20) - UDPHeader(8) .
#endif
#ifndef PUBLIC_COMMUNITY_STRING
#define PUBLIC_COMMUNITY_STRING "public"
#endif
#ifndef PRIVATE_COMMUNITY_STRING
#define PRIVATE_COMMUNITY_STRING "Segger"
#endif
//
// Task stack sizes that might not fit for some interfaces (multiples of sizeof(int)).
//
#ifndef APP_MAIN_STACK_SIZE
#define APP_MAIN_STACK_SIZE (3144)
#endif
/*********************************************************************
*
* Data types
*
**********************************************************************
*/
typedef struct {
IP_SNMP_SM_USM_USER_TABLE_ENTRY* pUser; // User handle to use. Use NULL for SNMPv1/SNMPv2c messages.
// For SNMPv3 please make sure to use a local (Engine) user
// for SNMPv3 TRAP messages. For SNMPv3 INFORM messages a
// peer (Engine) user has to be used.
IP_SNMP_AGENT_COMMUNITY* pCommunity; // Community handle to use. Use NULL for SNMPv3 messages.
U32 IPAddr; // IP addr. to send to.
U32 Timeout; // Timeout [ms].
U16 Port; // Port that listens on the Manager for TRAP/INFORM messages.
U16 DiscoverPort; // Port that listens on the Manager for Engine discover (get-request) messages.
U8 Type; // Type of message to send.
U8 Retries; // Retries for INFORM messages, ignored for other types.
} TRAP_INFORM_RECEIVERS;
/*********************************************************************
*
* Prototypes
*
**********************************************************************
*/
#ifdef __cplusplus
extern "C" { /* Make sure we have C-declarations in C++ programs */
#endif
void MainTask(void);
#ifdef __cplusplus
}
#endif
/*********************************************************************
*
* Static data
*
**********************************************************************
*/
static const U8 _aOID_IsoOrgDodInternetPrivateEnterprise[] = { 0x2B, 0x06, 0x01, 0x04, 0x01 }; // iso(1).org(3).dod(6).internet(1).private(4).enterprise(1) .
static const U8 _SnmpTrapColdStartOID[] = IP_SNMP_GENERIC_TRAP_OID_COLD_START;
static const U8 _abEnterpriseOID[] = { 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0xEA, 0x4A }; // iso(1).org(3).dod(6).internet(1).private(4).enterprise(1).ENCODED(PRIVATE_ENTERPRISE_NUMBER) .
#if (PRIVATE_ENTERPRISE_NUMBER != 46410)
//
// The byte representation in BER encoding for an OID can easily be converted
// from its decimal number using the IP_SNMP_AGENT_EncodeOIDValue() API.
//
#error Please change the enterprise ID of _abEnterpriseOID manually
#endif
//
// Permissions that are assigned to a registered community granting
// access rights. Specific OIDs not entered here inherit permissions
// from higher levels.
// The last line has to be present. The permissions of this line will
// be the default permissions if there are no parent OIDs with permissions.
//
#if (defined(__GNUC__)) || (defined(__SEGGER_CC__)) || (defined(__clang__))
#pragma GCC diagnostic ignored "-Wmissing-braces" // Avoid warning for missing braces with GCC as the GCC seems to think there is a problem with assigning values.
#endif
static const IP_SNMP_AGENT_PERM _aSNMP_Perm_Public[] = {
// Array containing OID , size of array , Permissions to grant.
&_aOID_IsoOrgDodInternetPrivateEnterprise[0], sizeof(_aOID_IsoOrgDodInternetPrivateEnterprise), IP_SNMP_AGENT_PERM_READ_MASK,
NULL , 0 , 0u
};
static const IP_SNMP_AGENT_PERM _aSNMP_Perm_Private[] = {
// Array containing OID, size of array , Permissions to grant.
&_abEnterpriseOID[0] , sizeof(_abEnterpriseOID), IP_SNMP_AGENT_PERM_READ_MASK | IP_SNMP_AGENT_PERM_WRITE_MASK,
NULL , 0 , 0u
};
//
// Generic IP variables.
//
static IP_HOOK_ON_STATE_CHANGE _StateChangeHook;
//
// Task stacks and Task-Control-Blocks.
//
static OS_STACKPTR int APP_MainStack[APP_MAIN_STACK_SIZE / sizeof(int)]; // Stack of the starting point of this sample.
static OS_TASK APP_MainTCB; // Task-Control-Block of the IP_Task.
static OS_STACKPTR int _IPStack[(TASK_STACK_SIZE_IP_TASK + 256)/sizeof(int)]; // Stack of the IP_Task.
static OS_TASK _IPTCB; // Task-Control-Block of the IP_Task.
#if USE_RX_TASK
static OS_STACKPTR int _IPRxStack[TASK_STACK_SIZE_IP_RX_TASK/sizeof(int)]; // Stack of the IP_RxTask.
static OS_TASK _IPRxTCB; // Task-Control-Block of the IP_RxTask.
#endif
//
// SNMP API locking resources.
//
static OS_RSEMA _SNMP_Lock;
//
// Memory blocks for SNMP community.
//
static IP_SNMP_AGENT_COMMUNITY _SNMP_Community_Public;
static IP_SNMP_AGENT_COMMUNITY _SNMP_Community_Private;
//
// Memory blocks for custom MIBs.
//
static IP_SNMP_AGENT_MIB _SNMP_SampleMIB;
//
// SNMP sample variables.
//
static U8 _LEDState;
//
// TRAP/INFORM receivers list that will receive a boot TRAP/INFORM
// message once the SNMP Agent has been initialized and is ready to
// handle incoming requests.
//
static IP_SNMP_AGENT_TRAP_INFORM_CONTEXT _aTrapInformConfig[MAX_TRAP_INFORM_RECEIVERS];
//
// TRAP/INFORM memory to use.
//
static IP_SNMP_AGENT_HOOK_ON_INFORM_RESPONSE _OnInformResponseHook;
static IP_SNMP_AGENT_CONTEXT _TrapInformVarbindContext;
//
// Buffer where the TRAP/INFORM Varbinds are temporarily stored.
// U32 to make sure we have a word alignment.
//
static U32 _abTrapInformVarbindBuffer[1472 / sizeof(U32)];
//
// MIB-II System memory for holding information strings.
//
static char _Mib2SystemSysContact[256] = "support@segger.com"; // For more information please refer to http://www.alvestrand.no/objectid/1.3.6.1.2.1.1.4.html .
static char _Mib2SystemSysName[256] = "snmpagent.segger.com"; // For more information please refer to http://www.alvestrand.no/objectid/1.3.6.1.2.1.1.5.html .
static char _Mib2SystemSysLocation[256] = "Your desktop"; // For more information please refer to http://www.alvestrand.no/objectid/1.3.6.1.2.1.1.6.html .
#if APP_ENABLE_SNMPV3_USM
static IP_SNMP_AGENT_MPV3_CONFIG _MPV3_Config = {
.MaxSize = 1400u
};
//
// SNMPv3 Engine
//
// The "SnmpEngineID" OCTET STRING in RFC 3412 is not limited in length
// and different implementations exist. RFC 3411 specifies the
// "SnmpEngineID" as "OCTET STRING (SIZE(5..32))". While we are not
// limited in the length of foreign "SnmpEngineID" fields in SNMP
// messages due to how we parse the message, it is suggested to stick
// to an RFC 3411 conform "SnmpEngineID". We construct an EngineId
// based on our 6 bytes MAC address as follows:
//
// - 4 bytes consisting of the PrivateEnterpriseNumber (PEN) with
// bit 31 set to indicate the EngineId is following RFC 3411 .
//
// - 1 byte with value 0x03, indicating that the rest of the
// EngineId is a 6 byte MAC address.
//
// - 6 bytes MAC address.
//
// Example for the EngineId for the SEGGER PEN 46410:
// 0x80 0x00 0xB5 0x4A 0x03 <6 byte MAC address>
//
// This sample uses the same AUTH/PRIV pass/key for all users
// and therefore only requires one buffer per hash/crypto combination.
// When implementing a real user database each entry should have its
// dedicated buffers instead.
//
static IP_SNMP_AGENT_SM_USM_CONFIG _SM_USM_Config;
static U32 _NextEngineTimeUpdate;
static U8 _abAuthKeyMD5[16]; // Big enough to hold a MD5(16) digest.
// The sample uses the same AUTH password for all users which
// resuslts in the same AuthKey for the same EngineId and
// hash algorithm used.
static U8 _abAuthKeySHA1[20]; // Same as above but only for a SHA-1 (20) digest.
static U8 _abPrivKeyMD5[16]; // Big enough to hold a MD5(16) digest.
// The sample uses the same PRIV password for all users which
// resuslts in the same PrivKey for the same EngineId and
// hash algorithm used.
static U8 _abPrivKeySHA1[20]; // Same as above but only for a SHA-1 (20) digest.
static U8 _abLocalEngineId[11];
//
// USM SNMP Engine entries
//
static IP_SNMP_SM_USM_ENGINE_ENTRY _LocalEngine;
//
// USM user AUTHentication
//
static CRYPTO_MD5_CONTEXT _MD5_Context;
static CRYPTO_SHA1_CONTEXT _SHA1_Context;
static CRYPTO_TDES_CONTEXT _DES_Context;
static IP_SNMP_SM_USM_USER_TABLE_ENTRY _USM_aUsers[MAX_USERS];
//
// Peer Engine variables for Engine discovery when sending INFORM messages.
//
static IP_SNMP_SM_USM_ENGINE_ENTRY _aInformEngine[MAX_TRAP_INFORM_RECEIVERS];
static U8 _aabInformAuthKey[MAX_TRAP_INFORM_RECEIVERS][20]; // Big enough to hold an MD5(16) or SHA1(20) digest.
static U8 _aabInformPrivKey[MAX_TRAP_INFORM_RECEIVERS][20]; // Big enough to hold an MD5(16) or SHA1(20) digest.
static U8 _aabInformPeerEngineId[MAX_TRAP_INFORM_RECEIVERS][32]; // RFC 3414 specifies EngineIds up to 32 bytes.
#endif
/*********************************************************************
*
* Configuration, TRAP/INFORM receivers
*
**********************************************************************
*/
//
// List/configuration of SNMP Managers that shall receive the boot TRAP/INFORM message.
// For SNMPv3 users and which index of the user table they use please refer to _USM_CreateUserTable() .
// Without implementing a dedicated user lookup database, the TRAP/INFORM receiver list and user table
// are unfortunately separated by other code in this sample.
//
static const TRAP_INFORM_RECEIVERS _aTrapInformReceivers[] = {
//
// SNMPv1 TRAP
//
{
NULL, // User handle to use. Use NULL for SNMPv1/SNMPv2c messages.
// For SNMPv3 please make sure to use a local (Engine) user
// for SNMPv3 TRAP messages. For SNMPv3 INFORM messages a
// peer (Engine) user has to be used.
&_SNMP_Community_Public, // Community handle to use. Use NULL for SNMPv3 messages.
IP_BYTES2ADDR(192, 168, 11, 202), // IP addr. to send to.
1000u, // Timeout [ms].
TRAP_INFORM_PORT, // Port that listens on the Manager for TRAP/INFORM messages.
ENGINE_DISCOVER_PORT, // Port that listens on the Manager for Engine discover (get-request) messages.
IP_SNMP_PDU_TYPE_TRAPV1, // Type of message to send.
5 // Retries for INFORM messages, ignored for other types.
}
//
// SNMPv2 TRAP
//
,{
NULL, // User handle to use. Use NULL for SNMPv1/SNMPv2c messages.
// For SNMPv3 please make sure to use a local (Engine) user
// for SNMPv3 TRAP messages. For SNMPv3 INFORM messages a
// peer (Engine) user has to be used.
&_SNMP_Community_Public, // Community handle to use. Use NULL for SNMPv3 messages.
IP_BYTES2ADDR(192, 168, 11, 202), // IP addr. to send to.
1000u, // Timeout [ms].
TRAP_INFORM_PORT, // Port that listens on the Manager for TRAP/INFORM messages.
ENGINE_DISCOVER_PORT, // Port that listens on the Manager for Engine discover (get-request) messages.
IP_SNMP_PDU_TYPE_TRAPV2, // Type of message to send.
5 // Retries for INFORM messages, ignored for other types.
}
//
// SNMPv2 INFORM
//
,{
NULL, // User handle to use. Use NULL for SNMPv1/SNMPv2c messages.
// For SNMPv3 please make sure to use a local (Engine) user
// for SNMPv3 TRAP messages. For SNMPv3 INFORM messages a
// peer (Engine) user has to be used.
&_SNMP_Community_Public, // Community handle to use. Use NULL for SNMPv3 messages.
IP_BYTES2ADDR(192, 168, 11, 202), // IP addr. to send to.
1000u, // Timeout [ms].
TRAP_INFORM_PORT, // Port that listens on the Manager for TRAP/INFORM messages.
ENGINE_DISCOVER_PORT, // Port that listens on the Manager for Engine discover (get-request) messages.
IP_SNMP_PDU_TYPE_INFORMV2, // Type of message to send.
5 // Retries for INFORM messages, ignored for other types.
}
#if APP_ENABLE_SNMPV3_USM
//
// SNMPv3 TRAP (uses SNMPv2 TRAP PDU type)
//
,{
&_USM_aUsers[2], // User handle to use. Use NULL for SNMPv1/SNMPv2c messages.
// For SNMPv3 please make sure to use a local (Engine) user
// for SNMPv3 TRAP messages. For SNMPv3 INFORM messages a
// peer (Engine) user has to be used.
NULL, // Community handle to use. Use NULL for SNMPv3 messages.
IP_BYTES2ADDR(192, 168, 11, 202), // IP addr. to send to.
1000u, // Timeout [ms].
TRAP_INFORM_PORT, // Port that listens on the Manager for TRAP/INFORM messages.
ENGINE_DISCOVER_PORT, // Port that listens on the Manager for Engine discover (get-request) messages.
IP_SNMP_PDU_TYPE_TRAPV2, // Type of message to send.
5 // Retries for INFORM messages, ignored for other types.
}
//
// SNMPv3 INFORM (uses SNMPv2 INFORM PDU type)
//
,{
&_USM_aUsers[2], // User handle to use. Use NULL for SNMPv1/SNMPv2c messages.
// For SNMPv3 please make sure to use a local (Engine) user
// for SNMPv3 TRAP messages. For SNMPv3 INFORM messages a
// peer (Engine) user has to be used.
NULL, // Community handle to use. Use NULL for SNMPv3 messages.
IP_BYTES2ADDR(192, 168, 11, 202), // IP addr. to send to.
1000u, // Timeout [ms].
TRAP_INFORM_PORT, // Port that listens on the Manager for TRAP/INFORM messages.
ENGINE_DISCOVER_PORT, // Port that listens on the Manager for Engine discover (get-request) messages.
IP_SNMP_PDU_TYPE_INFORMV2, // Type of message to send.
5 // Retries for INFORM messages, ignored for other types.
}
#endif
};
/*********************************************************************
*
* Local functions
*
**********************************************************************
*/
/*********************************************************************
*
* _OnStateChange()
*
* Function description
* Callback that will be notified once the state of an interface
* changes.
*
* Parameters
* IFaceId : Zero-based interface index.
* AdminState: Is this interface enabled ?
* HWState : Is this interface physically ready ?
*/
static void _OnStateChange(unsigned IFaceId, U8 AdminState, U8 HWState) {
//
// Check if this is a disconnect from the peer or a link down.
// In this case call IP_Disconnect() to get into a known state.
//
if (((AdminState == IP_ADMIN_STATE_DOWN) && (HWState == 1)) || // Typical for dial-up connection e.g. PPP when closed from peer. Link up but app. closed.
((AdminState == IP_ADMIN_STATE_UP) && (HWState == 0))) { // Typical for any Ethernet connection e.g. PPPoE. App. opened but link down.
IP_Disconnect(IFaceId); // Disconnect the interface to a clean state.
}
}
/*********************************************************************
*
* _SNMP_cbInit()
*
* Function description
* Init locking mechanism used by the SNMP Agent API.
*/
static void _SNMP_cbInit(void) {
OS_CREATERSEMA(&_SNMP_Lock);
}
/*********************************************************************
*
* _SNMP_cbDeInit()
*
* Function description
* De-init locking mechanism used by the SNMP Agent API.
*/
static void _SNMP_cbDeInit(void) {
OS_DeleteRSema(&_SNMP_Lock);
}
/*********************************************************************
*
* _SNMP_cbLock()
*
* Function description
* Acquires the lock to ensure SNMP API is thread safe.
*/
static void _SNMP_cbLock(void) {
OS_Use(&_SNMP_Lock);
}
/*********************************************************************
*
* _SNMP_cbUnlock()
*
* Function description
* Releases a previously acquired lock for thread safety SNMP API.
*/
static void _SNMP_cbUnlock(void) {
OS_Unuse(&_SNMP_Lock);
}
/*********************************************************************
*
* _SNMP_cbAllocSendBuffer()
*
* Function description
* Allocates a buffer that is used to store the complete message
* to send.
*
* Parameters
* pUserContext: User specific context passed to the process message API.
* ppBuffer : Pointer where to store the data pointer to the buffer.
* NumBytes : Size of the buffer to allocate for our message.
* IPAddrLen : Length of the IP address used to send to the next host.
* A length of 4 means an IPv4 host to send to, a length
* of 16 means sending to an IPv6 host.
*
* Return value
* Error: NULL
* O.K. : Handle to allocated buffer. Might be the same as stored in ppBuffer.
*
* Additional information
* The buffer can be a static buffer that can then be sent using the
* socket interface. Another option would be to allocate a zero-copy
* packet to avoid unnecessary copy operations.
*/
static void* _SNMP_cbAllocSendBuffer(void* pUserContext, U8** ppBuffer, U32 NumBytes, U8 IPAddrLen) {
IP_PACKET* pPacket;
int IFaceId;
IP_USE_PARA(pUserContext);
IP_USE_PARA(IPAddrLen);
IFaceId = IP_INFO_GetNumInterfaces() - 1; // Get the last registered interface ID as this is most likely the interface we want to use in this sample.
pPacket = IP_UDP_AllocEx(IFaceId, NumBytes);
if (pPacket == NULL) {
IP_SNMP_AGENT_APP_WARN(("Unable to alloc packet for TRAP/INFORM."));
} else {
*ppBuffer = pPacket->pData;
}
return pPacket;
}
/*********************************************************************
*
* _SNMP_cbFreeSendBuffer()
*
* Function description
* Frees a send buffer that has been previously allocated.
*
* Parameters
* pUserContext: User specific context passed to the process message API.
* p : Handle to free.
* SendCalled : Was the send callback used ?
* r : Return value of the send callback if it has been used.
*
* Additional information
* If the allocated buffer was a zero-copy buffer, the free operation
* is typically handled by the stack on sending and this routine does
* not have to do anything at all. The same applies to a static buffer.
*/
static void _SNMP_cbFreeSendBuffer(void* pUserContext, void* p, char SendCalled, int r) {
IP_USE_PARA(pUserContext);
IP_USE_PARA(r);
//
// Only free the buffer if it has not been done by the send routine.
//
if (SendCalled == 0) {
IP_UDP_Free((IP_PACKET*)p);
}
}
/*********************************************************************
*
* _SNMP_cbSendTrapInform()
*
* Function description
* Sends a TRAP/INFORM message.
*
* Parameters
* pContext : Send context, typically a socket.
* pUserContext: User specific context passed to the process message API.
* hBuffer : Pointer to the buffer handle. Might be an IP_PACKET pointer.
* pData : Pointer to the data to send.
* NumBytes : Length of message to send.
* pIPAddr : Pointer to the IP address to send to in network order.
* Port : Foreign port to send to in host order.
* IPAddrLen : Length of the IP address at pIPAddr. A length of 4
* means an IPv4 host to send to, a length of 16 means
* sending to an IPv6 host.
*
* Return value
* O.K. : >= 0
* Error: < 0
*/
static int _SNMP_cbSendTrapInform(void* pContext, void* pUserContext, void* hBuffer, const U8* pData, U32 NumBytes, U8* pIPAddr, U16 Port, U8 IPAddrLen) {
IP_PACKET* pPacket;
int r;
int IFaceId;
IP_USE_PARA(pContext);
IP_USE_PARA(pUserContext);
IP_USE_PARA(IPAddrLen);
pPacket = (IP_PACKET*)hBuffer;
SNMP_MEMCPY(pPacket->pData, pData, NumBytes);
pPacket->NumBytes = NumBytes;
IFaceId = IP_INFO_GetNumInterfaces() - 1; // Get the last registered interface ID as this is most likely the interface we want to use in this sample.
r = IP_UDP_SendAndFree(IFaceId, *(U32*)pIPAddr, Port, TRAP_INFORM_PORT, pPacket);
return r;
}
/*********************************************************************
*
* _SNMP_cbGetTime()
*
* Function description
* Retrieves the current system time in milliseconds.
*
* Return value
* System time [ms].
*/
static U32 _SNMP_cbGetTime(void) {
return OS_GetTime32();
}
/*********************************************************************
*
* _SNMP_cbSysTicks2SnmpTime()
*
* Function description
* Converts a timestamp in system ticks (typically 1ms) into a
* 1/100 seconds SNMP timestamp.
*
* Return value
* Tick counts in 1/100 seconds.
*/
static U32 _SNMP_cbSysTicks2SnmpTime(U32 SysTicks) {
return (SysTicks / 10);
}
/*********************************************************************
*
* _SNMP_cbSnmpTime2SysTicks()
*
* Function description
* Converts 1/100 seconds SNMP timestamp into a system tick
* timestamp (typically 1ms).
*
* Return value
* System ticks.
*/
static U32 _SNMP_cbSnmpTime2SysTicks(U32 SnmpTime) {
return (SnmpTime * 10);
}
/*********************************************************************
*
* _SNMP_cbMib2System_GetSysUpTime()
*
* Function description
* Retrieves the tick count of the system in 1/100 seconds since
* the network management portion of the system was last
* re-initialized.
*
* Return value
* Tick counts in 1/100 seconds since re-init.
*/
static U32 _SNMP_cbMib2System_GetSysUpTime(void) {
//
// For simplicity this is the time since boot.
//
return _SNMP_cbSysTicks2SnmpTime(_SNMP_cbGetTime());
}
/*********************************************************************
*
* _SNMP_cbMib2System_GetSetSysContact()
*
* Function description
* Retrieves/sets the system.sysContact information.
*
* Parameters
* pBuffer : IsWrite == 0: Pointer to buffer where to store the string.
* IsWrite == 1: Pointer to buffer where to read a string.
* pNumBytes: IsWrite == 0: Pointer to size of the buffer at pBuffer and where to store the length of the string (without termination).
* IsWrite == 1: Pointer where to find the length of the string (without termination).
* IsWrite : 0: Variable read, other parameters are used for output.
* 1: Variable write, other parameters are used for input.
*
* Return value
* O.K. : 0
* Error: Other
*
* Additional information
* - On IsWrite==1 values that shall be used later on need to be preserved manually.
* - Max. allowed string length is up to 255 printable characters.
* - http://www.alvestrand.no/objectid/1.3.6.1.2.1.1.4.html
*/
static int _SNMP_cbMib2System_GetSetSysContact(char* pBuffer, U32* pNumBytes, char IsWrite) {
U32 NumBytes;
if (IsWrite == 0) {
//
// Read value for sending.
//
NumBytes = SNMP_STRLEN(_Mib2SystemSysContact); // Count how long our string is.
NumBytes = SEGGER_MIN(NumBytes, *pNumBytes); // Limit to the buffer size.
SNMP_MEMCPY(pBuffer, &_Mib2SystemSysContact[0], NumBytes); // Copy string.
*pNumBytes = NumBytes; // Store the length of the string.
} else {
//
// Save value received.
//
NumBytes = SEGGER_MIN((sizeof(_Mib2SystemSysContact) - 1), *pNumBytes); // Calc. how many bytes shall be stored/can be stored.
SNMP_MEMCPY(&_Mib2SystemSysContact[0], pBuffer, NumBytes); // Copy string.
_Mib2SystemSysContact[NumBytes] = '\0'; // Terminate string.
}
return 0; // O.K.
}
/*********************************************************************
*
* _SNMP_cbMib2System_GetSetSysName()
*
* Function description
* Retrieves/sets the system.sysName information.
*
* Parameters
* pBuffer : IsWrite == 0: Pointer to buffer where to store the string.
* IsWrite == 1: Pointer to buffer where to read a string.
* pNumBytes: IsWrite == 0: Pointer to size of the buffer at pBuffer and where to store the length of the string (without termination).
* IsWrite == 1: Pointer where to find the length of the string (without termination).
* IsWrite : 0: Variable read, other parameters are used for output.
* 1: Variable write, other parameters are used for input.
*
* Return value
* O.K. : 0
* Error: Other
*
* Additional information
* - On IsWrite==1 values that shall be used later on need to be preserved manually.
* - Max. allowed string length is up to 255 printable characters.
* - http://www.alvestrand.no/objectid/1.3.6.1.2.1.1.5.html
*/
static int _SNMP_cbMib2System_GetSetSysName(char* pBuffer, U32* pNumBytes, char IsWrite) {
U32 NumBytes;
if (IsWrite == 0) {
//
// Read value for sending.
//
NumBytes = SNMP_STRLEN(_Mib2SystemSysName); // Count how long our string is.
NumBytes = SEGGER_MIN(NumBytes, *pNumBytes); // Limit to the buffer size.
SNMP_MEMCPY(pBuffer, &_Mib2SystemSysName[0], NumBytes); // Copy string.
*pNumBytes = NumBytes; // Store the length of the string.
} else {
//
// Save value received.
//
NumBytes = SEGGER_MIN((sizeof(_Mib2SystemSysName) - 1), *pNumBytes); // Calc. how many bytes shall be stored/can be stored.
SNMP_MEMCPY(&_Mib2SystemSysName[0], pBuffer, NumBytes); // Copy string.
_Mib2SystemSysName[NumBytes] = '\0'; // Terminate string.
}
return 0; // O.K.
}
/*********************************************************************
*
* _SNMP_cbMib2System_GetSetSysLocation()
*
* Function description
* Retrieves/sets the system.sysLocation information.
*
* Parameters
* pBuffer : IsWrite == 0: Pointer to buffer where to store the string.
* IsWrite == 1: Pointer to buffer where to read a string.
* pNumBytes: IsWrite == 0: Pointer to size of the buffer at pBuffer and where to store the length of the string (without termination).
* IsWrite == 1: Pointer where to find the length of the string (without termination).
* IsWrite : 0: Variable read, other parameters are used for output.
* 1: Variable write, other parameters are used for input.
*
* Return value
* O.K. : 0
* Error: Other
*
* Additional information
* - On IsWrite==1 values that shall be used later on need to be preserved manually.
* - Max. allowed string length is up to 255 printable characters.
* - http://www.alvestrand.no/objectid/1.3.6.1.2.1.1.6.html
*/
static int _SNMP_cbMib2System_GetSetSysLocation(char* pBuffer, U32* pNumBytes, char IsWrite) {
U32 NumBytes;
if (IsWrite == 0) {
//
// Read value for sending.
//
NumBytes = SNMP_STRLEN(_Mib2SystemSysLocation); // Count how long our string is.
NumBytes = SEGGER_MIN(NumBytes, *pNumBytes); // Limit to the buffer size.
SNMP_MEMCPY(pBuffer, &_Mib2SystemSysLocation[0], NumBytes); // Copy string.
*pNumBytes = NumBytes; // Store the length of the string.
} else {
//
// Save value received.
//
NumBytes = SEGGER_MIN((sizeof(_Mib2SystemSysLocation) - 1), *pNumBytes); // Calc. how many bytes shall be stored/can be stored.
SNMP_MEMCPY(&_Mib2SystemSysLocation[0], pBuffer, NumBytes); // Copy string.
_Mib2SystemSysLocation[NumBytes] = '\0'; // Terminate string.
}
return 0; // O.K.
}
/*********************************************************************
*
* _SNMP_cbSample()
*
* Function description
* Callback handler that can be assign to one or more MIBs.
*
* Parameters
* pContext : Context for the current message and response.
* pUserContext: User specific context passed to the process message API.
* pMIB : Pointer to the MIB that is currently accessed.
* MIBLen : Length of the MIB that is currently accessed.
* pIndex : Pointer to the encoded index of the OID. Might be NULL
* in case of a getnext-request.
* IndexLen : Length of the encoded index in bytes.
* RequestType : IP_SNMP_PDU_SET_REQUEST or
* IP_SNMP_PDU_GET_REQUEST or
* IP_SNMP_PDU_TYPE_GET_NEXT_REQUEST .
* VarType : Type of variable that waits to be parsed for input data.
* Only valid if RequestType is IP_SNMP_PDU_SET_REQUEST .
*
* Return value
* Everything O.K. : IP_SNMP_OK
* In case of an error: IP_SNMP_ERR_*
*
* Additional information
* - pIndex might point to more than one index OID value e.g. when
* using multi dimensional arrays. In any case the index should
* be decoded before it is used to make sure values above 127
* are correctly used.
* - Parameters of a set-request need to be stored back with their new value.
* - The memory that pMIB and pIndex point to might be reused by store
* functions. If the data stored at their location is important you
* have to save them locally on your own. It is advised to do all
* checks at the beginning of the callback so you do not rely on these
* parameters while or after you use store functions.
*/
static int _SNMP_cbSample(IP_SNMP_AGENT_CONTEXT* pContext, void* pUserContext, const U8* pMIB, U32 MIBLen, const U8* pIndex, U32 IndexLen, U8 RequestType, U8 VarType) {
I32 OnOff;
U32 Index;
U32 NumBytesDecoded;
U8 LEDMask;
(void)pUserContext; // Context passed through the whole SNMP API by the application.
(void)pMIB; // OID part with the address of the MIB found.
(void)MIBLen; // Length of the MIB OID.
Index = 0u;
LEDMask = 0u;
NumBytesDecoded = IndexLen;
if (pIndex != NULL) {
//
// Decode index if available. Typically the only case where it is not available
// is when the first index available is requested for a getnext-request.
//
if (IP_SNMP_AGENT_DecodeOIDValue(pIndex, &NumBytesDecoded, &Index, &pIndex) != 0) {
return IP_SNMP_ERR_GENERIC;
}
}
if (RequestType == IP_SNMP_PDU_TYPE_GET_NEXT_REQUEST) { // Handle getnext-request.
//
// A getnext-request has to provide the next indexed item after the one addressed.
// As for this sample we expect to have only a one dimensional index this is simply
// incrementing the read index by one.
// For a getnext-request the callback has to store the OID value of the new item
// as well.
// In general the callback has the following options how to react:
// 1) The MIB callback is not provided with an index : The callback has to store the first available OID and its value.
// 2) The MIB callback is able to store the next item after the given index: The callback has to store the OID and the value of the next item.
// 3) The MIB callback is NOT able to store the next item after the given index: The callback has to store an NA exception or to report an IP_SNMP_ERR_NO_CREATION error.
//
if (pIndex == NULL) { // Case 1).
Index = 0; // Return the first index available.
} else {
Index++; // Return the next index available after the one provided.
}
}
//
// Do some checks.
//
if ((Index > 8) || // This sample is designed for an index of 0..8 where 0 is the status of all LEDs and 1..8 is a LED index.
(IndexLen > NumBytesDecoded)) { // We expect to have only one index, if there are more index bytes to parse this means an error (trying to access .x.y where only .x is available).
if (IP_SNMP_AGENT_StoreInstanceNA(pContext) != 0) {
return IP_SNMP_ERR_TOO_BIG;
}
return IP_SNMP_OK;
//
// As alternate IP_SNMP_ERR_NO_CREATION can be returned but
// will abort processing of the VarbindList.
//
// return IP_SNMP_ERR_NO_CREATION; // This resource does not exist.
}
if (Index != 0) {
LEDMask = (1 << (Index - 1));
}
//
// Process get-, getnext- or set-request.
//
if (RequestType == IP_SNMP_PDU_TYPE_SET_REQUEST) { // Handle set-request.
//
// Check that the parameter type of the variable is what we expect.
//
if (VarType != IP_SNMP_TYPE_INTEGER) {
return IP_SNMP_ERR_WRONG_TYPE;
}
if (IP_SNMP_AGENT_ParseInteger(pContext, &OnOff) != 0) {
return IP_SNMP_ERR_GENERIC;
}
//
// Set LED state based on index:
// Index 0: Invalid.
// Other : Set LED state for one specific LED by one byte.
//
if (Index == 0) {
return IP_SNMP_ERR_NO_ACCESS;
} else {
//
// Set status of one LED.
//
if (OnOff != 0) { // On ?
_LEDState |= LEDMask;
BSP_SetLED(Index - 1);
} else {
_LEDState &= (LEDMask ^ 255);
BSP_ClrLED(Index - 1);
}
if (IP_SNMP_AGENT_StoreInteger(pContext, OnOff) != 0) {
return IP_SNMP_ERR_TOO_BIG;
}
}
} else { // Handle get-request.
if (RequestType == IP_SNMP_PDU_TYPE_GET_NEXT_REQUEST) {
//
// Store OID of "next" item returned if this is for a getnext-request.
// The value will be stored by the following code in case of a getnext-
// and a get-request.
//
if (IP_SNMP_AGENT_StoreCurrentMibOidAndIndex(pContext, pContext, 1, Index) != 0) {
return IP_SNMP_ERR_TOO_BIG;
}
}
//
// Get LED state based on index:
// Index 0: Get LED state for all 8 possible LEDs in one byte.
// Other : Get LED state for one specific LED in one byte.
//
if (Index == 0) {
if (IP_SNMP_AGENT_StoreInteger(pContext, _LEDState) != 0) {
return IP_SNMP_ERR_TOO_BIG;
}
} else {
//
// Return status of one LED.
//
if ((_LEDState & LEDMask) != 0) {
OnOff = 1;
} else {
OnOff = 0;
}
if (IP_SNMP_AGENT_StoreInteger(pContext, OnOff) != 0) {
return IP_SNMP_ERR_TOO_BIG;
}
}
}
return IP_SNMP_OK;
}
#if APP_ENABLE_SNMPV3_USM
/*********************************************************************
*
* _MD5_cbInit()
*
* Function description
* Returns/initializes a fresh hash context.
*
* Return value
* Initialized hash context.
*
* Additional information
* Calculating hashes is done from a task that uses the API lock.
* Therefore it is typically sufficient to use a single static
* hash context.
*
* For the moment the routine is not expected to fail and
* return a NULL pointer.
*
* Currently CRYPTO_Init() is only necessary to be called for
* algorithms using the OS layer as this is initialized during
* the init. To reduce dependencies we do not call it here which
* saves us the OS layer in case emCrypt is not used elsewhere
* in the product.
*/
static void* _MD5_cbInit(void) {
// CRYPTO_Init();
CRYPTO_MD5_Install(NULL, &CRYPTO_HASH_MD5_SW);
CRYPTO_MD5_Init(&_MD5_Context);
return SEGGER_PTR2PTR(void, &_MD5_Context);
}
/*********************************************************************
*
* _MD5_cbAdd()
*
* Function description
* Adds data to the hash calculation.
*
* Parameters
* pContext: Pointer to hash context returned from init callback.
* pInput : Pointer to data to add.
* InputLen: Length of the data to add from pInput .
*/
static void _MD5_cbAdd(void* pContext, const U8* pInput, unsigned InputLen) {
CRYPTO_MD5_Add(SEGGER_PTR2PTR(CRYPTO_MD5_CONTEXT, pContext), pInput, InputLen);
}
/*********************************************************************
*
* _MD5_cbFinal()
*
* Function description
* Finalizes the hash calculation and returns the digest.
*
* Parameters
* pContext : Pointer to hash context returned from init callback.
* pDigest : Pointer where to store the result.
* DigestLen: Maximum size of the buffer where to store the result.
*/
static void _MD5_cbFinal(void* pContext, U8* pDigest, unsigned DigestLen) {
CRYPTO_MD5_Final(SEGGER_PTR2PTR(CRYPTO_MD5_CONTEXT, pContext), pDigest, DigestLen);
}
/*********************************************************************
*
* _SHA1_cbInit()
*
* Function description
* Returns/initializes a fresh hash context.
*
* Return value
* Initialized hash context.
*
* Additional information
* Calculating hashes is done from a task that uses the API lock.
* Therefore it is typically sufficient to use a single static
* hash context.
*
* For the moment the routine is not expected to fail and
* return a NULL pointer.
*
* Currently CRYPTO_Init() is only necessary to be called for
* algorithms using the OS layer as this is initialized during
* the init. To reduce dependencies we do not call it here which
* saves us the OS layer in case emCrypt is not used elsewhere
* in the product.
*/
static void* _SHA1_cbInit(void) {
// CRYPTO_Init();
CRYPTO_SHA1_Install(NULL, &CRYPTO_HASH_SHA1_SW);
CRYPTO_SHA1_Init(&_SHA1_Context);
return SEGGER_PTR2PTR(void, &_SHA1_Context);
}
/*********************************************************************
*
* _SHA1_cbAdd()
*
* Function description
* Adds data to the hash calculation.
*
* Parameters
* pContext: Pointer to hash context returned from init callback.
* pInput : Pointer to data to add.
* InputLen: Length of the data to add from pInput .
*/
static void _SHA1_cbAdd(void* pContext, const U8* pInput, unsigned InputLen) {
CRYPTO_SHA1_Add(SEGGER_PTR2PTR(CRYPTO_SHA1_CONTEXT, pContext), pInput, InputLen);
}
/*********************************************************************
*
* _SHA1_cbFinal()
*
* Function description
* Finalizes the hash calculation and returns the digest.
*
* Parameters
* pContext : Pointer to hash context returned from init callback.
* pDigest : Pointer where to store the result.
* DigestLen: Maximum size of the buffer where to store the result.
*/
static void _SHA1_cbFinal(void* pContext, U8* pDigest, unsigned DigestLen) {
CRYPTO_SHA1_Final(SEGGER_PTR2PTR(CRYPTO_SHA1_CONTEXT, pContext), pDigest, DigestLen);
}
/*********************************************************************
*
* _DES_cbInit()
*
* Function description
* Returns/initializes a fresh cipher context for a decrypt or
* encrypt operation.
*
* Parameters
* pKey : Pointer to the cipher key to use. Its length is
* determined by the PRIV(acy) cipher selected via the
* user table.
* KeyLen : Length of the key at pKey .
* Direction: Decrypt or encrypt direction of type IP_SNMP_CIPHER_DIR .
* * IP_SNMP_CIPHER_DIR_DECRYPT
* * IP_SNMP_CIPHER_DIR_ENCRYPT
*
* Return value
* Initialized cipher context.
*
* Additional information
* Decrypt/encrypt is done from a task that uses the API lock.
* Therefore it is typically sufficient to use a single static
* cipher context.
*
* For the moment the routine is not expected to fail and
* return a NULL pointer.
*
* Currently CRYPTO_Init() is only necessary to be called for
* algorithms using the OS layer as this is initialized during
* the init. To reduce dependencies we do not call it here which
* saves us the OS layer in case emCrypt is not used elsewhere
* in the product.
*/
static void* _DES_cbInit(const U8* pKey, unsigned KeyLen, IP_SNMP_CIPHER_DIR Direction) {
// CRYPTO_Init();
CRYPTO_TDES_Install(NULL, &CRYPTO_CIPHER_TDES_SW);
if (Direction == IP_SNMP_CIPHER_DIR_DECRYPT) {
CRYPTO_TDES_InitDecrypt(&_DES_Context, pKey, KeyLen);
} else {
CRYPTO_TDES_InitEncrypt(&_DES_Context, pKey, KeyLen);
}
return SEGGER_PTR2PTR(void, &_DES_Context);
}
/*********************************************************************
*
* _DES_cbExec()
*
* Function description
* Decrypts/encrypts data.
*
* Parameters
* pContext : Pointer to cipher context returned from init callback.
* pOutput : Pointer where to store the decrypted output.
* pInput : Pointer to the encrypted input.
* InputLen : Length of the data to decrypt from pInput .
* pIV : Pointer to the IV (InitializationVector) to use. The
* size of the IV is determined by the PRIV(acy) cipher
* selected via the user table.
* Direction: Decrypt or encrypt direction of type IP_SNMP_CIPHER_DIR .
* * IP_SNMP_CIPHER_DIR_DECRYPT
* * IP_SNMP_CIPHER_DIR_ENCRYPT
*/
static void _DES_cbExec(void* pContext, U8* pOutput, const U8* pInput, unsigned InputLen, U8* pIV, IP_SNMP_CIPHER_DIR Direction) {
CRYPTO_TDES_CONTEXT* pDESContext;
pDESContext = SEGGER_PTR2PTR(CRYPTO_TDES_CONTEXT, pContext);
if (Direction == IP_SNMP_CIPHER_DIR_DECRYPT) {
CRYPTO_CIPHER_CBC_Decrypt(pDESContext, pOutput, pInput, InputLen, pIV, pDESContext->pAPI);
} else {
CRYPTO_CIPHER_CBC_Encrypt(pDESContext, pOutput, pInput, InputLen, pIV, pDESContext->pAPI);
}
}
/*********************************************************************
*
* _DES_cbFinal()
*
* Function description
* Finalizes the decrypt or encrypt operation.
*
* Parameters
* pContext : Pointer to hash context returned from init callback.
* Direction: Decrypt or encrypt direction of type IP_SNMP_CIPHER_DIR .
* * IP_SNMP_CIPHER_DIR_DECRYPT
* * IP_SNMP_CIPHER_DIR_ENCRYPT
*
* Additional information
* This callback can be used to free resources allocated during init
* or to kill any security related leftovers from the cipher operation.
*/
static void _DES_cbFinal(void* pContext, IP_SNMP_CIPHER_DIR Direction) {
IP_SNMP_AGENT_USE_PARA(pContext);
IP_SNMP_AGENT_USE_PARA(Direction);
}
/*********************************************************************
*
* _SNMP_cbOnInformReport()
*
* Function description
* Callback executed whenever a REPORT with new information about
* an SNMPv3 Engine is received for a pending INFORM.
*
* Parameters
* pContext : Pointer to an SNMP Agent context. The read
* pointer in the context is ready to parse the
* first and typically only VarBind present in
* a REPORT message, the reason for the report.
* pInformContext: Pointer to the INFORM context of type
* IP_SNMP_AGENT_TRAP_INFORM_CONTEXT for which new
* Engine information have been discovered.
* pUserContext : User specific context passed to the process
* message API.
* pInfo : Pointer to information received about an Engine.
*
* Return value
* == 0: Retry to send the INFORMs when returning from the callback.
* < 0: Do not access pInformContext when returning from the callback (removed?).
*
* Additional information
* This callback gets executed when a REPORT message with (new) SNMPv3
* Engine information for a yet to be sent INFORM is received.
* This is typically the case when sending an INFORM while only
* knowing the IP address of the receiving Manager but not the EngineId.
* Another case where a REPORT is received is when the peer Engine
* time window was missed. In this case the REPORT lets us know the
* current time of the peer Engine and we have to send the INFORM
* again after updating our information about the peer Engine time
* which prevents replay attacks with old messages if the
* AUTH(thorization) security level is used.
*
* Once new Engine information is received the Engine table
* maintained by the application should be updated and the INFORM
* should either be resent immediately from within this callback
* or is resent by the retry mechanism for INFORM messages.
*
* Once initial EngineBoots and EngineTime values have been
* discovered for an Engine they can be maintained locally by
* the application to prevent the discover part being necessary
* if the authoritative Engine is happy with what we send. In
* the worst case we will receive a REPORT by the peer Engine
* telling us the latest information.
*/
static int _SNMP_cbOnInformReport(IP_SNMP_AGENT_CONTEXT* pContext, IP_SNMP_AGENT_TRAP_INFORM_CONTEXT* pInformContext, void* pUserContext, IP_SNMP_USM_ENGINE_INFO* pInfo) {
const char* pReason;
const U8* pData;
IP_SNMP_SM_USM_USER_TABLE_ENTRY* pUser;
IP_SNMP_SM_USM_ENGINE_ENTRY* pEngine;
U32 Len;
unsigned EngineIdLen;
U8 Reason;
IP_USE_PARA(pUserContext);
EngineIdLen = pInfo->Engine.EngineIdLen;
if (EngineIdLen > sizeof(_aabInformPeerEngineId[0])) {
IP_SNMP_AGENT_APP_WARN(("Unable to store discovered EngineId with %u bytes.", EngineIdLen));
} else {
//
// Update the Engine parameters for the currently processed INFORM message.
//
pEngine = SEGGER_PTR2PTR(IP_SNMP_SM_USM_ENGINE_ENTRY, pInformContext->pUser->pEngine);
if (pEngine == NULL) {
IP_SNMP_AGENT_APP_WARN(("When changing the sample to dynamic alloc for Engines, please change this place too."));
for (;;) {
//
// PANIC halt.
//
}
}
pEngine->EngineIdLen = EngineIdLen;
SNMP_MEMCPY(SEGGER_PTR2PTR(U8, pEngine->pEngineId), pInfo->Engine.pEngineId, EngineIdLen);
pEngine->EngineBoots = pInfo->Engine.EngineBoots;
pEngine->EngineTime = pInfo->Engine.EngineTime;
//
// Update the User entry AUTH and PRIV keys based on the new EngineId if necessary.
//
pUser = pInformContext->pUser;
if (pInformContext->pUser->pAuthParams != NULL) {
(void)IP_SNMP_AGENT_SM_USM_CalcKey( pUser->pAuthParams, SEGGER_PTR2PTR(U8, pUser->pAuthKey), sizeof(_aabInformAuthKey[0]), pEngine->pEngineId, pEngine->EngineIdLen, (const U8*)AUTH_PASSWORD, sizeof(AUTH_PASSWORD) - 1u);
if (pInformContext->pUser->pPrivParams != NULL) {
(void)IP_SNMP_AGENT_SM_USM_CalcKey(pUser->pAuthParams, SEGGER_PTR2PTR(U8, pUser->pPrivKey), sizeof(_aabInformPrivKey[0]), pEngine->pEngineId, pEngine->EngineIdLen, (const U8*)PRIV_PASSWORD, sizeof(PRIV_PASSWORD) - 1u);
}
}
//
// Try to analyze the REPORT and output some more information.
// Extract the first (and typcially) only OID.
//
if (IP_SNMP_AGENT_ParseOID(pContext, &pData, &Len) == 0) {
//
// The OID with the reason for the REPORT is typically
// iso(1).org(3).dod(6).internet(1).snmpV2(6).snmpModules(3).snmpUsmMIB(15).usmMIBObjects(1).usmStats(1).x.0
// wheras x represents the node with the reason and a Counter32 how often this has occurred.
//
if (Len == 10u) { // The first two nodes are combined, so 11 nodes but 10 bytes expected.
Reason = *(pData + 8);
switch (Reason) {
case 1u:
pReason = "unsupported security level";
break;
case 2u:
pReason = "not in time window";
break;
case 3u:
pReason = "unknown Username";
break;
case 4u:
pReason = "unknown EngineId";
break;
case 5u:
pReason = "wrong digest";
break;
default:
pReason = NULL;
break;
}
if (pReason != NULL) {
IP_SNMP_AGENT_APP_LOG(("REPORT with reason \"%s\" received.", pReason));
}
}
}
}
return 0; // Retry sending the INFORM again.
}
/*********************************************************************
*
* _GetFreeUserEngineAndBuffersIndex()
*
* Function description
* Retrieves the indexes for a new User and Engine entry as well
* as INFORM buffers to be used when sending INFORMs to peer Engines
* for which we have to learn their EngineId, EngineBoots and
* EngineTime values.
*
* Parameters
* ppUser : Pointer where to store the pointer to a free
* entry in the User table.
* pEngineBufferIndex: Pointer where to store the index to free
* buffers for INFORM/REPORT handling.
*
* Return value
* == 0: O.K., free entries found.
* < 0: Error.
*/
static int _GetFreeUserEngineAndBuffersIndex(IP_SNMP_SM_USM_USER_TABLE_ENTRY** ppUser, unsigned* pEngineBufferIndex) {
IP_SNMP_SM_USM_USER_TABLE_ENTRY* pUser;
IP_SNMP_SM_USM_ENGINE_ENTRY* pEngine;
int r;
unsigned i;
r = -1; // Assume error.
//
// Find a free User entry first.
//
i = 0u;
pUser = &_USM_aUsers[0];
do {
if (pUser->UsernameLen == 0u) { // Free entry (placeholder) ?
*ppUser = pUser;
//
// Now find a set of free INFORM/REPORT buffers.
//
i = 0u;
pEngine = &_aInformEngine[0];
do {
if (pEngine->EngineIdLen == 0u) { // Free entry (placeholder) ?
*pEngineBufferIndex = i;
r = 0; // O.K.
break;
}
pEngine++;
} while (++i < (int)MAX_TRAP_INFORM_RECEIVERS);
break;
}
pUser++;
} while (++i < SEGGER_COUNTOF(_USM_aUsers));
return r;
}
#endif // APP_ENABLE_SNMPV3_USM
/*********************************************************************
*
* _SNMP_AGENT_SendBootTrap()
*
* Function description
* Sends a boot trap to a list of Managers. This shall be done once
* the SNMP Agent is up and running to inform Managers about it.
*
* Additional information
* This can be done to inform SNMP Managers that our Agent is now
* up and running. This is a sample implementation that by default
* configuration of this sample sends a coldBoot trap with one
* additional custom Varbind to the Managers.
*/
static void _SNMP_AGENT_SendBootTrap(void) {
#if APP_ENABLE_SNMPV3_USM
IP_SNMP_SM_USM_USER_TABLE_ENTRY* pUser;
IP_SNMP_SM_USM_USER_TABLE_ENTRY* pNewUser;
unsigned NewBufferIndex;
#endif
unsigned i;
U32 Timeout;
int r;
int IFaceId;
U8 ab[sizeof(_abEnterpriseOID) + 1];
#if APP_ENABLE_SNMPV3_USM
U8 MPFlags;
#endif
#if APP_ENABLE_SNMPV3_USM
//
// Hook in the callback to handle our INFORM Engine discovery.
//
IP_SNMP_AGENT_SetInformReportCallback(_SNMP_cbOnInformReport);
#endif
IFaceId = IP_INFO_GetNumInterfaces() - 1; // Get the last registered interface ID as this is most likely the interface we want to use in this sample.
//
// Prepare TRAP/INFORM context and custom Varbinds to send.
// The TRAP/INFORM context can be reused to send the same Varbinds to multiple Managers.
//
IP_SNMP_AGENT_PrepareTrapInform(&_TrapInformVarbindContext, NULL, &_abEnterpriseOID[0], sizeof(_abEnterpriseOID), &_SnmpTrapColdStartOID[0], sizeof(_SnmpTrapColdStartOID), (U8*)&_abTrapInformVarbindBuffer[0], sizeof(_abTrapInformVarbindBuffer), IP_GetIPAddr(IFaceId));
//
// Store one custom Varbind (OID & value).
//
SNMP_MEMCPY(&ab[0], &_abEnterpriseOID[0], sizeof(_abEnterpriseOID));
IP_SNMP_AGENT_OpenVarbind(&_TrapInformVarbindContext); // Step 1: Open Varbind (store Varbind header).
ab[sizeof(_abEnterpriseOID)] = 0; // Set index of OID to store to .0 .
IP_SNMP_AGENT_StoreOID(&_TrapInformVarbindContext, (const U8*)ab, sizeof(_abEnterpriseOID) + 1, sizeof(ab), 0); // Step 2: Store the OID value into the Varbind.
IP_SNMP_AGENT_StoreInteger(&_TrapInformVarbindContext, 12345); // Step 3: Store the value sent in the Varbind.
IP_SNMP_AGENT_CloseVarbind(&_TrapInformVarbindContext); // Step 4: Close the Varbind (calculate Varbind length and enter it into the Varbind header). Repeat step 1 - 4 to store further Varbinds.
//
// Send TRAP/INFORM messages to Managers.
//
for (i = 0u; i < SEGGER_COUNTOF(_aTrapInformReceivers); i++) {
//
// Prepare the next TRAP/INFORM receiver.
//
IP_SNMP_AGENT_TRAP_INFORM_SetIPv4AddrPort (&_aTrapInformConfig[i], _aTrapInformReceivers[i].IPAddr, _aTrapInformReceivers[i].Port, _aTrapInformReceivers[i].DiscoverPort);
IP_SNMP_AGENT_TRAP_INFORM_SetType (&_aTrapInformConfig[i], _aTrapInformReceivers[i].Type);
IP_SNMP_AGENT_TRAP_INFORM_SetCommunity (&_aTrapInformConfig[i], _aTrapInformReceivers[i].pCommunity);
IP_SNMP_AGENT_TRAP_INFORM_SetTimeoutRetries(&_aTrapInformConfig[i], _aTrapInformReceivers[i].Timeout, _aTrapInformReceivers[i].Retries);
#if APP_ENABLE_SNMPV3_USM
pUser = _aTrapInformReceivers[i].pUser;
//
// Check that no placeholder entry is used by accident.
//
if (pUser != NULL) {
if (pUser->UsernameLen == 0u) {
IP_SNMP_AGENT_APP_WARN(("Invalid user (placeholder entry?) used for sending INFORM."));
for (;;) {
//
// PANIC halt.
//
}
}
}
//
// For SNMPv3 INFORM messages this sample is designed to reuse local
// Engine Users and their configuration for sending INFORMs to peer
// Engines. For this we create a copy of the local User and the
// configuration and recalculate the AUTH and PRIV keys for the
// peer Engine if necessary.
//
if ((_aTrapInformReceivers[i].Type == IP_SNMP_PDU_TYPE_INFORMV2) && (pUser != NULL)) {
unsigned DigestLen;
//
// Make sure that other SNMP Agent API calls do not interfere with this.
//
_SNMP_cbLock();
//
// Get a free User entry and buffers for the copy.
//
r = _GetFreeUserEngineAndBuffersIndex(&pNewUser, &NewBufferIndex);
if (r < 0) {
IP_SNMP_AGENT_APP_WARN(("No more free User entries for sending INFORM."));
for (;;) {
//
// PANIC halt.
//
}
}
//
// Copy the User, config and AUTH/PRIV buffers so we can update the
// entries from the OnEngineInfo callback when receiving a REPORT.
//
SNMP_MEMCPY(pNewUser, pUser, sizeof(*pUser));
//
// Replace the Engine with an empty entry so we can discover the peer Engine
// using _SNMP_cbOnInformReport() when receiving a REPORT.
//
SNMP_MEMSET(&_aInformEngine[NewBufferIndex], 0, sizeof(_aInformEngine[NewBufferIndex]));
_aInformEngine[NewBufferIndex].pEngineId = &_aabInformPeerEngineId[NewBufferIndex][0];
//
// Copy AUTH/PRIV to new buffers.
//
if (pNewUser->pAuthParams != NULL) {
//
// Copy the existing AUTH parameters.
//
DigestLen = pNewUser->pAuthParams->pAuthAPI->DigestLen;
if ((DigestLen > sizeof(_aabInformAuthKey[0])) || (DigestLen > sizeof(_aabInformPrivKey[0]))) {
IP_SNMP_AGENT_APP_WARN(("Copy buffer for AUTH/PRIV key to send INFORM is too small."));
for (;;) {
//
// PANIC halt.
//
}
}
SNMP_MEMCPY(&_aabInformAuthKey[NewBufferIndex], pNewUser->pAuthKey, DigestLen);
if (pNewUser->pPrivParams != NULL) {
//
// Copy the existing PRIV parameters.
//
SNMP_MEMCPY(&_aabInformPrivKey[NewBufferIndex], pNewUser->pPrivKey, DigestLen);
}
}
//
// Switch the INFORM to the copied User and link AUTH/PRIV to
// the copied buffers for AUTH/PRIV for this user.
//
SNMP_MEMSET(&_aInformEngine[NewBufferIndex], 0, sizeof(_aInformEngine[0]));
IP_SNMP_SM_USM_USER_SetEngine(pNewUser, &_aInformEngine[NewBufferIndex]);
IP_SNMP_SM_USM_USER_SetAuthParamsAndKey(pNewUser, pNewUser->pAuthParams, &_aabInformAuthKey[NewBufferIndex][0]);
IP_SNMP_SM_USM_USER_SetPrivParamsAndKey(pNewUser, pNewUser->pPrivParams, &_aabInformPrivKey[NewBufferIndex][0]);
pUser = pNewUser;
//
// Now it is safe to call other SNMP Agent API again.
//
_SNMP_cbUnlock();
}
//
// For SNMPv3 receivers with a User, the TRAP/INFORM generated
// by this sample uses the highest security level that the user
// entry allows. Security however is optional and a lower
// security level can be used if the Manager receiving the
// TRAP/INFORM is O.K. with this.
//
IP_SNMP_AGENT_TRAP_INFORM_SetUser(&_aTrapInformConfig[i], pUser);
MPFlags = 0u;
if (pUser != NULL) {
if (pUser->pAuthParams != NULL) {
MPFlags |= IP_SNMPV3_MSG_FLAG_AUTH_MASK;
}
if (pUser->pPrivParams != NULL) {
MPFlags |= IP_SNMPV3_MSG_FLAG_PRIV_MASK;
}
}
IP_SNMP_AGENT_TRAP_INFORM_SetMPFlags(&_aTrapInformConfig[i], MPFlags);
#endif
//
// Send the TRAP/INFORM or if necessary for SNMPv3 a peer Engine discover
// followed by the actual INFORM from the resend logic once the peer Engine
// has been discovered.
//
r = IP_SNMP_AGENT_SendTrapInform(NULL, &_TrapInformVarbindContext, &_aTrapInformConfig[i]);
if (r < 0) {
//
// Error.
//
IP_SNMP_AGENT_APP_WARN(("Failed to send TRAP/INFORM."));
//
// Sending a message can fail at several stages.
// As the message might still be queued for sending
// IP_SNMP_AGENT_CancelInform() should be called to
// make sure resending is stopped and the message
// really is unlinked from all queues.
//
IP_SNMP_AGENT_CancelInform(&_aTrapInformConfig[i]);
} else if (r == 0) {
//
// Message successfully sent. If the receiver settings have
// been allocated they can now be freed.
//
IP_SNMP_AGENT_APP_LOG(("TRAP sent. Varbind context and config can be freed."));
} else if (r == 1) {
//
// Message successfully sent. The receiver settings need to
// be preserved until IP_SNMP_AGENT_CheckInformStatus() returns
// that work on the receiver has been done or a hook registered
// with IP_SNMP_AGENT_AddInformReponseHook() signals this item,
// either with success or with an error.
//
IP_SNMP_AGENT_APP_LOG(("INFORM sent. Varbind context and config need to be preserved."));
//
// Wait for INFORM to be ACKed before we continue. In this sample we only
// send one TRAP/INFORM message after another to avoid eating up all of
// our zero-copy packet buffers when we send all TRAP/INFORM at the same time.
// If _SNMP_AllocSendBuffer() returns allocated memory or uses multiple
// buffers, the following steps can be done at any other time.
//
while (1) {
r = IP_SNMP_AGENT_CheckInformStatus(&_aTrapInformConfig[i]);
if (r == IP_SNMP_AGENT_INFORM_STATUS_WAITING_FOR_ACK) {
//
// Call exec here manually as we are blocking our own task by waiting here.
//
Timeout = IP_SNMP_AGENT_Exec();
OS_Delay(Timeout);
} else {
if (r == IP_SNMP_AGENT_INFORM_STATUS_TIMEOUT) {
IP_SNMP_AGENT_APP_LOG(("INFORM timeout."));
} else if (r == IP_SNMP_AGENT_INFORM_STATUS_CANCELED) {
IP_SNMP_AGENT_APP_LOG(("INFORM canceled by application."));
} else if (r == IP_SNMP_AGENT_INFORM_STATUS_ACK_RECEIVED) {
IP_SNMP_AGENT_APP_LOG(("INFORM ACK received."));
} else if (r == IP_SNMP_AGENT_INFORM_STATUS_NACK_RECEIVED) {
IP_SNMP_AGENT_APP_LOG(("INFORM NACK received."));
}
IP_SNMP_AGENT_APP_LOG(("Varbind context and config can be freed."));
break;
}
}
}
}
}
/*********************************************************************
*
* _SNMP_AGENT_OnRx()
*
* Function description
* OnRx callback that receives incoming INFORM responses and handles
* SNMP request messages and sending back to them.
*
* Parameters
* pInPacket: Pointer to packet.
* pContext : Pointer to context if any (typically NULL).
*
* Return value
* Packet is valid : IP_OK
* Packet is invalid for some reason: IP_RX_ERROR
*/
static int _SNMP_AGENT_OnRx(IP_PACKET* pInPacket, void* pContext) {
U8* pInData;
U8* pOutData;
IP_PACKET* pOutPacket;
U32 IPAddr;
U32 MaxPacketSize;
int ResponseLen;
int IFaceId;
U16 LPort;
U16 FPort;
U16 NumBytes;
IP_USE_PARA(pContext);
//
// Get information about the data received. One of them is the port on which
// the packet came in which can be used to check if it is a request or an INFORM ACK.
//
IP_UDP_GetSrcAddr(pInPacket, &IPAddr, sizeof(IPAddr));
LPort = IP_UDP_GetLPort(pInPacket);
FPort = IP_UDP_GetFPort(pInPacket);
pInData = (U8*)IP_UDP_GetDataPtr(pInPacket);
NumBytes = IP_UDP_GetDataSize(pInPacket);
if ((LPort == MESSAGE_PORT) || (LPort == TRAP_INFORM_PORT)) {
//
// Handle the SNMP REQUEST, INFORM or REPORT.
// To keep things simple we always have to be able to allocate a packet
// for a potential response, even if we might end up not needing one
// for example if this was an ACK for one of our INFORMs.
//
IFaceId = IP_INFO_GetNumInterfaces() - 1; // Get the last registered interface ID as this is most likely the interface we want to use in this sample.
MaxPacketSize = IP_GetMaxAvailPacketSize(IFaceId);
if (MaxPacketSize == 0) {
IP_SNMP_AGENT_APP_WARN(("No free packet for response."));
} else {
MaxPacketSize -= 28; // Minus IPv4(20 bytes) and UDP(8 bytes) header length.
pOutPacket = IP_UDP_AllocEx(IFaceId, MaxPacketSize);
if (pOutPacket == NULL) {
IP_SNMP_AGENT_APP_WARN(("Unable to alloc packet for response."));
} else {
pOutData = (U8*)IP_UDP_GetDataPtr(pOutPacket);
ResponseLen = IP_SNMP_AGENT_ProcessRequest(pInData, NumBytes, pOutData, MaxPacketSize, NULL);
//
// Send back a response if we have to.
//
if (ResponseLen > 0) {
pOutPacket->NumBytes = ResponseLen;
IP_UDP_SendAndFree(IFaceId, IPAddr, FPort, LPort, pOutPacket);
} else {
IP_UDP_Free(pOutPacket);
}
}
}
} else {
return IP_RX_ERROR; // Error, unkown packet received.
}
return IP_OK;
}
/*********************************************************************
*
* _SNMP_AGENT_OnInformResponse()
*
* Function description
* Callback that is activated upon a change in an INFORM message
* waiting for an ACK to be received.
*
* Parameters
* pUserContext : User specific context set when creating the context.
* pVarbindContext : Pointer to context that holds the custom Varbinds to
* send in the INFORM.
* pTrapInformContext: Pointer to the context of the INFORM message.
* Status : IP_SNMP_AGENT_INFORM_STATUS_TIMEOUT
* IP_SNMP_AGENT_INFORM_STATUS_CANCELED
* IP_SNMP_AGENT_INFORM_STATUS_ACK_RECEIVED
* IP_SNMP_AGENT_INFORM_STATUS_NACK_RECEIVED
*/
static void _SNMP_AGENT_OnInformResponse(void* pUserContext, IP_SNMP_AGENT_CONTEXT* pVarbindContext, IP_SNMP_AGENT_TRAP_INFORM_CONTEXT* pTrapInformContext, int Status) {
IP_USE_PARA(pUserContext);
IP_USE_PARA(pVarbindContext);
IP_USE_PARA(pTrapInformContext);
if (Status == IP_SNMP_AGENT_INFORM_STATUS_TIMEOUT) {
IP_SNMP_AGENT_APP_LOG(("INFORM timeout."));
} else if (Status == IP_SNMP_AGENT_INFORM_STATUS_CANCELED) {
IP_SNMP_AGENT_APP_LOG(("INFORM canceled by application."));
} else if (Status == IP_SNMP_AGENT_INFORM_STATUS_ACK_RECEIVED) {
IP_SNMP_AGENT_APP_LOG(("INFORM ACK received."));
} else if (Status == IP_SNMP_AGENT_INFORM_STATUS_NACK_RECEIVED) {
IP_SNMP_AGENT_APP_LOG(("INFORM NACK received."));
}
IP_SNMP_AGENT_APP_LOG(("Varbind context and config can be freed."));
}
/*********************************************************************
*
* Local API structures
*
**********************************************************************
*/
//
// API callbacks to feed the SNMP Agent with user information.
//
static const IP_SNMP_AGENT_API _SNMPAgentApi = {
_SNMP_cbInit, // pfInit, typically used to initialize a lock API.
_SNMP_cbDeInit, // pfDeInit, typically used to deinitialize a lock API.
_SNMP_cbLock, // pfLock .
_SNMP_cbUnlock, // pfUnlock .
_SNMP_cbAllocSendBuffer, // pfAllocSendBuffer .
_SNMP_cbFreeSendBuffer, // pfFreeSendBuffer .
_SNMP_cbSendTrapInform, // pfSendTrapInform .
_SNMP_cbGetTime, // pfGetTime .
_SNMP_cbSysTicks2SnmpTime, // pfSysTicks2SnmpTime .
_SNMP_cbSnmpTime2SysTicks // pfSnmpTime2SysTicks .
};
static const IP_SNMP_AGENT_MIB2_SYSTEM_API _SNMPAgentMib2SystemApi = {
"emNet SNMP Agent with emNet and embOS", // system.sysDescr .
_abEnterpriseOID, // system.sysObjectID, pointer to oid value .
SEGGER_COUNTOF(_abEnterpriseOID), // system.sysObjectID, length of oid value .
_SNMP_cbMib2System_GetSysUpTime, // system.sysUpTime .
_SNMP_cbMib2System_GetSetSysContact, // system.sysContact .
_SNMP_cbMib2System_GetSetSysName, // system.sysName .
_SNMP_cbMib2System_GetSetSysLocation, // system.sysLocation .
72 // sysServices .
};
#if APP_ENABLE_SNMPV3_USM
//
// API structures for SNMPv3 USM AUTH(entication) and PRIV(acy) .
//
static const IP_SNMP_HASH_API _USM_HashMD5 = {
_MD5_cbInit, // pfInit
_MD5_cbAdd, // pfAdd
_MD5_cbFinal, // pfFinal
};
static const IP_SNMP_SM_USM_AUTH_PARAMS _USM_AuthMD5 = {
&IP_SNMP_SM_USM_AuthMD5, // pAuthAPI
&_USM_HashMD5 // pHashAPI
};
static const IP_SNMP_HASH_API _USM_HashSHA1 = {
_SHA1_cbInit, // pfInit
_SHA1_cbAdd, // pfAdd
_SHA1_cbFinal, // pfFinal
};
static const IP_SNMP_SM_USM_AUTH_PARAMS _USM_AuthSHA1 = {
&IP_SNMP_SM_USM_AuthSHA1, // pAuthAPI
&_USM_HashSHA1 // pHashAPI
};
static const IP_SNMP_CIPHER_API _USM_CipherDES = {
_DES_cbInit, // pfInit
_DES_cbExec, // pfExec
_DES_cbFinal // pfFinal
};
static const IP_SNMP_SM_USM_PRIV_PARAMS _USM_PrivDES = {
&IP_SNMP_SM_USM_PrivDES, // pPrivAPI
&_USM_CipherDES // pCipherAPI
};
/*********************************************************************
*
* _USM_FillUserTable()
*
* Function description
* Initializes/creates the SNMPv3 USM user table with local Users.
*
* Additional information
* Only Users for the local Engine are added here. For SNMPv3 INFORM
* demonstration in this sample local Users and their configuration
* are copied to a new User entry when preparing the TRAP/INFORM
* messages to send. This is done to be able to easily configure
* one and the same user and configuration parameters. With a real
* User/Engine database targeting a real SNMP environment Users and
* their peer Engine entries should be added to the User table in
* the same way as local Engine Users.
*
* Entries not used are recognized when their Username length is
* set to zero.
*/
static void _USM_FillUserTable(void) {
unsigned Index;
SNMP_MEMSET(&_USM_aUsers[0], 0, sizeof(_USM_aUsers));
Index = 0u;
//
// The sample uses different usernames to demonstrate different levels of access and
// different hash algorithms used. Each of these users shares the same AUTH password
// and the same PRIV password in this sample. This results in the same AuthKey and
// PrivKey being calculated for the very same EngineId and hash algorithm.
//
(void)IP_SNMP_AGENT_SM_USM_CalcKey(&_USM_AuthMD5 , &_abAuthKeyMD5[0] , sizeof(_abAuthKeyMD5) , _LocalEngine.pEngineId, _LocalEngine.EngineIdLen, (const U8*)AUTH_PASSWORD, sizeof(AUTH_PASSWORD) - 1u);
(void)IP_SNMP_AGENT_SM_USM_CalcKey(&_USM_AuthMD5 , &_abPrivKeyMD5[0] , sizeof(_abPrivKeyMD5) , _LocalEngine.pEngineId, _LocalEngine.EngineIdLen, (const U8*)PRIV_PASSWORD, sizeof(PRIV_PASSWORD) - 1u);
(void)IP_SNMP_AGENT_SM_USM_CalcKey(&_USM_AuthSHA1, &_abAuthKeySHA1[0], sizeof(_abAuthKeySHA1), _LocalEngine.pEngineId, _LocalEngine.EngineIdLen, (const U8*)AUTH_PASSWORD, sizeof(AUTH_PASSWORD) - 1u);
(void)IP_SNMP_AGENT_SM_USM_CalcKey(&_USM_AuthSHA1, &_abPrivKeySHA1[0], sizeof(_abPrivKeySHA1), _LocalEngine.pEngineId, _LocalEngine.EngineIdLen, (const U8*)PRIV_PASSWORD, sizeof(PRIV_PASSWORD) - 1u);
//
// Add local "testuser" with "noAuthNoPriv" and read only permissions.
//
// Example for snmpset to clear the first LED:
// snmpset -v3 -u testuser -l noAuthNoPriv <AgentIP> 1.3.6.1.4.1.46410.1 i 0
//
IP_SNMP_SM_USM_USER_SetEngine (&_USM_aUsers[Index], &_LocalEngine);
IP_SNMP_SM_USM_USER_SetUsername (&_USM_aUsers[Index], (const U8*)"testuser", sizeof("testuser") - 1u);
IP_SNMP_SM_USM_USER_SetPerm (&_USM_aUsers[Index], &_aSNMP_Perm_Public[0]);
Index++;
//
// Add local "testuser_authNoPriv_MD5" with "authNoPriv" with AUTH(MD5) and read and write permissions.
//
// Example for snmpset to clear the first LED:
// snmpset -v3 -u testuser_authNoPriv_MD5 -l authNoPriv -a MD5 -A AUTHpass <AgentIP> 1.3.6.1.4.1.46410.1 i 0
//
IP_SNMP_SM_USM_USER_SetEngine (&_USM_aUsers[Index], &_LocalEngine);
IP_SNMP_SM_USM_USER_SetUsername (&_USM_aUsers[Index], (const U8*)"testuser_authNoPriv_MD5", sizeof("testuser_authNoPriv_MD5") - 1u);
IP_SNMP_SM_USM_USER_SetAuthParamsAndKey(&_USM_aUsers[Index], &_USM_AuthMD5, &_abAuthKeyMD5[0]);
IP_SNMP_SM_USM_USER_SetPerm (&_USM_aUsers[Index], &_aSNMP_Perm_Private[0]);
Index++;
//
// Add local "testuser_authPriv_MD5" with "authPriv" with AUTH(MD5) and read and write permissions.
//
// Example for snmpset to clear the first LED:
// snmpset -v3 -u testuser_authPriv_MD5 -l authPriv -a MD5 -A AUTHpass -x DES -X PRIVpass <AgentIP> 1.3.6.1.4.1.46410.1 i 0
//
IP_SNMP_SM_USM_USER_SetEngine (&_USM_aUsers[Index], &_LocalEngine);
IP_SNMP_SM_USM_USER_SetUsername (&_USM_aUsers[Index], (const U8*)"testuser_authPriv_MD5", sizeof("testuser_authPriv_MD5") - 1u);
IP_SNMP_SM_USM_USER_SetAuthParamsAndKey(&_USM_aUsers[Index], &_USM_AuthMD5, &_abAuthKeyMD5[0]);
IP_SNMP_SM_USM_USER_SetPrivParamsAndKey(&_USM_aUsers[Index], &_USM_PrivDES, &_abPrivKeyMD5[0]);
IP_SNMP_SM_USM_USER_SetPerm (&_USM_aUsers[Index], &_aSNMP_Perm_Private[0]);
Index++;
//
// Add local "testuser_authNoPriv_SHA1" with "authNoPriv" with AUTH(SHA-1) and read and write permissions.
//
// Example for snmpset to clear the first LED:
// snmpset -v3 -u testuser_authNoPriv_SHA1 -l authNoPriv -a SHA -A AUTHpass <AgentIP> 1.3.6.1.4.1.46410.1 i 0
//
IP_SNMP_SM_USM_USER_SetEngine (&_USM_aUsers[Index], &_LocalEngine);
IP_SNMP_SM_USM_USER_SetUsername (&_USM_aUsers[Index], (const U8*)"testuser_authNoPriv_SHA1", sizeof("testuser_authNoPriv_SHA1") - 1u);
IP_SNMP_SM_USM_USER_SetAuthParamsAndKey(&_USM_aUsers[Index], &_USM_AuthSHA1, &_abAuthKeySHA1[0]);
IP_SNMP_SM_USM_USER_SetPerm (&_USM_aUsers[Index], &_aSNMP_Perm_Private[0]);
Index++;
//
// Add local "testuser_authPriv_SHA1" with "authPriv" with AUTH(SHA-1) and read and write permissions.
//
// Example for snmpset to clear the first LED:
// snmpset -v3 -u testuser_authPriv_SHA1 -l authPriv -a SHA -A AUTHpass -x DES -X PRIVpass <AgentIP> 1.3.6.1.4.1.46410.1 i 0
//
IP_SNMP_SM_USM_USER_SetEngine (&_USM_aUsers[Index], &_LocalEngine);
IP_SNMP_SM_USM_USER_SetUsername (&_USM_aUsers[Index], (const U8*)"testuser_authPriv_SHA1", sizeof("testuser_authPriv_SHA1") - 1u);
IP_SNMP_SM_USM_USER_SetAuthParamsAndKey(&_USM_aUsers[Index], &_USM_AuthSHA1, &_abAuthKeySHA1[0]);
IP_SNMP_SM_USM_USER_SetPrivParamsAndKey(&_USM_aUsers[Index], &_USM_PrivDES , &_abPrivKeySHA1[0]);
IP_SNMP_SM_USM_USER_SetPerm (&_USM_aUsers[Index], &_aSNMP_Perm_Private[0]);
Index++;
//
// Add local "testuser_view" with "noAuthNoPriv" and read only permissions.
// This demonstrates "view-based" permissions where the same username gains different permissions
// based on the security level ("noAuthNoPriv", "authNoPriv" or "authPriv").
//
// Example for snmpset to clear the first LED:
// snmpset -v3 -u testuser_view -l noAuthNoPriv <AgentIP> 1.3.6.1.4.1.46410.1 i 0
//
IP_SNMP_SM_USM_USER_SetEngine (&_USM_aUsers[Index], &_LocalEngine);
IP_SNMP_SM_USM_USER_SetUsername (&_USM_aUsers[Index], (const U8*)"testuser_view", sizeof("testuser_view") - 1u);
IP_SNMP_SM_USM_USER_SetPerm (&_USM_aUsers[Index], &_aSNMP_Perm_Public[0]);
Index++;
//
// Add local "testuser_view" with "authNoPriv" with AUTH(MD5) and read and write permissions.
// This demonstrates "view-based" permissions where the same username gains different permissions
// based on the security level ("noAuthNoPriv", "authNoPriv" or "authPriv").
//
// Example for snmpset to clear the first LED:
// snmpset -v3 -u testuser_view -l authNoPriv -a MD5 -A AUTHpass <AgentIP> 1.3.6.1.4.1.46410.1 i 0
//
IP_SNMP_SM_USM_USER_SetEngine (&_USM_aUsers[Index], &_LocalEngine);
IP_SNMP_SM_USM_USER_SetUsername (&_USM_aUsers[Index], (const U8*)"testuser_view", sizeof("testuser_view") - 1u);
IP_SNMP_SM_USM_USER_SetAuthParamsAndKey(&_USM_aUsers[Index], &_USM_AuthMD5, &_abAuthKeyMD5[0]);
IP_SNMP_SM_USM_USER_SetPerm (&_USM_aUsers[Index], &_aSNMP_Perm_Private[0]);
Index++;
//
// Sanity check for out of index usage in sample.
//
if (Index > MAX_USERS) {
IP_SNMP_AGENT_APP_WARN(("Configuration exceeds userlist max. entries"));
for (;;) {
//
// PANIC halt.
//
}
}
}
#endif // APP_ENABLE_SNMPV3_USM
/*********************************************************************
*
* _SNMP_AGENT_InitConfig()
*
* Function description
* Initializes and configures the SNMP Agent.
*/
static void _SNMP_AGENT_InitConfig(void) {
int r;
#if APP_ENABLE_SNMPV3_USM
int IFaceId;
#endif
//
// Check that we do not exceed our TRAP/INFORM list contexts.
//
if (SEGGER_COUNTOF(_aTrapInformReceivers) > MAX_TRAP_INFORM_RECEIVERS) {
IP_SNMP_AGENT_APP_LOG(("Configured TRAP/INFORM receivers exceed MAX_TRAP_INFORM_RECEIVERS (%u).", MAX_TRAP_INFORM_RECEIVERS));
while (1) {
OS_Delay(100);
}
}
//
// Init the SNMP Agent.
//
IP_SNMP_AGENT_Init(&_SNMPAgentApi);
#if APP_ENABLE_SNMPV3_USM
//
// Add SNMPv3 support.
//
IP_SNMP_AGENT_MPV3_Add(&_MPV3_Config);
//
// Generate a local SNMP EngineId.
//
SEGGER_WrU32BE(&_abLocalEngineId[0], PRIVATE_ENTERPRISE_NUMBER); // Add our PEN.
_abLocalEngineId[0] |= (1u << 7); // Mark as RFC 3411 conform.
_abLocalEngineId[4] = 0x03; // MAC address is used for additional identification.
IFaceId = IP_INFO_GetNumInterfaces() - 1; // Get the last registered interface ID as this is most likely the interface we want to use in this sample.
IP_GetHWAddr(IFaceId, &_abLocalEngineId[5], 6u); // Add our MAC address.
//
// Set initial EngineBoots and EngineTime values.
// These can either be values saved on the previous SNMP shutdown
// or completely new values. Of course some sort of randomization
// adds to the security. For more information about these parameters
// please refer to the description of the IP_SNMP_SM_USM_ENGINE_ENTRY
// structure.
//
_LocalEngine.pEngineId = (const U8*)&_abLocalEngineId[0];
_LocalEngine.EngineIdLen = sizeof(_abLocalEngineId);
_LocalEngine.EngineBoots = 1;
_LocalEngine.EngineTime = 0;
_NextEngineTimeUpdate = _SNMP_cbGetTime() + 1000u; // Update the SNMP time once per second.
//
// Configure and add SNMPv3 User-basedSecurityModel (USM) support.
//
_SM_USM_Config.pLocalEngine = &_LocalEngine;
_SM_USM_Config.Timeout = ENGINE_AUTH_TIMEOUT;
IP_SNMP_AGENT_SM_USM_Add(&_SM_USM_Config);
//
// Create and set the USM user table.
//
_USM_FillUserTable();
(void)IP_SNMP_AGENT_SM_USM_SetUserTable(SEGGER_PTR2PTR(const IP_SNMP_SM_USM_USER_TABLE_ENTRY, &_USM_aUsers[0]), SEGGER_COUNTOF(_USM_aUsers));
#endif // APP_ENABLE_SNMPV3_USM
//
// Add the MIB-II tree iso(1).org(3).dod(6).internet(1).ietf(2).mib2(1).system(1) .
//
IP_SNMP_AGENT_AddMIB_IsoOrgDodInternetIetfMib2System(&_SNMPAgentMib2SystemApi);
//
// Add the MIB-II tree iso(1).org(3).dod(6).internet(1).ietf(2).mib2(1).interfaces(2) .
//
IP_SNMP_AGENT_AddMIB_IsoOrgDodInternetIetfMib2Interfaces(&IP_SNMP_AGENT_MIB2_INTERFACES_emNet);
//
// Add the default MIB tree iso(1).org(3).dod(6).internet(1).private(4).enterprise(1) .
//
IP_SNMP_AGENT_AddMIB_IsoOrgDodInternetPrivateEnterprise();
//
// Add a private MIB with OID iso(1).org(3).dod(6).internet(1).private(4).enterprise(1).PRIVATE_ENTERPRISE_NUMBER .
//
r = IP_SNMP_AGENT_AddMIB(&_aOID_IsoOrgDodInternetPrivateEnterprise[0], sizeof(_aOID_IsoOrgDodInternetPrivateEnterprise), &_SNMP_SampleMIB, _SNMP_cbSample, PRIVATE_ENTERPRISE_NUMBER);
if (r != 0) {
IP_SNMP_AGENT_APP_LOG(("Failed to add sample private MIB."));
while (1) {
OS_Delay(100);
}
}
//
// Add a first SNMPv1/SNMPv2c community string (without termination) and set permissions for it.
//
IP_SNMP_AGENT_AddCommunity (&_SNMP_Community_Public, PUBLIC_COMMUNITY_STRING, sizeof(PUBLIC_COMMUNITY_STRING) - 1);
IP_SNMP_AGENT_SetCommunityPerm(&_SNMP_Community_Public, &_aSNMP_Perm_Public[0]); // Set permissions for public community (typically read permissions).
//
// Add a second SNMPv1/SNMPv2c community string (without termination) and set permissions for it.
//
IP_SNMP_AGENT_AddCommunity (&_SNMP_Community_Private, PRIVATE_COMMUNITY_STRING, sizeof(PRIVATE_COMMUNITY_STRING) - 1);
IP_SNMP_AGENT_SetCommunityPerm(&_SNMP_Community_Private, &_aSNMP_Perm_Private[0]); // Set permissions for private community (typically read and write permissions).
//
// Register on INFORM response hook that signals timeouts and
// received INFORM responses for waiting items.
//
IP_SNMP_AGENT_AddInformReponseHook(&_OnInformResponseHook, &_SNMP_AGENT_OnInformResponse);
}
/*********************************************************************
*
* APP_MainTask()
*
* Function description
* Sample starting point.
*/
static void APP_MainTask(void) {
U32 Timeout;
OS_SetTaskName(OS_GetTaskID(), "SNMP Agent - Mgmt");
//
// Initialize and configure the SNMP Agent.
//
_SNMP_AGENT_InitConfig();
//
// Register SNMP Agent callbacks for message handling.
//
IP_UDP_Open(0uL /* any foreign host */, 0 /* any foreign port */, MESSAGE_PORT , _SNMP_AGENT_OnRx, NULL);
IP_UDP_Open(0uL /* any foreign host */, 0 /* any foreign port */, TRAP_INFORM_PORT, _SNMP_AGENT_OnRx, NULL);
//
// Send boot trap to a list of Managers to let them know we are alive.
//
_SNMP_AGENT_SendBootTrap();
//
// Handle management tasks of the SNMP Agent like resends.
// Also handles the EngineBoots and EngineTime values if SNMPv3 is used.
//
while (1) {
Timeout = IP_SNMP_AGENT_Exec(); // Returns a timeout of 1 second or earlier (if there is a pending action).
OS_Delay(Timeout);
#if APP_ENABLE_SNMPV3_USM
//
// Update the SNMP Engine time once per second.
// The SNMP time does not have to be timely perfect and a slight variance
// might even be beneficial against replay attacks.
//
Timeout = _NextEngineTimeUpdate;
if (IP_IsExpired(_NextEngineTimeUpdate) != 0u) {
_NextEngineTimeUpdate += 1000u; // Do not care when the check happens, just make sure we loosely stay in rhythm.
//
// Update the EngineTime by simply incrementing its seconds.
//
_LocalEngine.EngineTime++;
//
// Once the I32 overflows into negative, we reset the EngineTime
// back to 0 and increment EngineBoots .
//
if (_LocalEngine.EngineTime < 0) {
_LocalEngine.EngineTime = 0;
//
// The maximum for EngineBoots as an I32 is 2147483647 .
// According to RFC 3414, manual interaction is needed once EngineBoots and EngineTime both reach their maximum.
// We simply reset EngineBoots back to 1 in this case as this will automatically result in the next request not
// being in our time window (default +-150 seconds from NOW of the current EngineBoot cycle).
//
_LocalEngine.EngineBoots++;
if (_LocalEngine.EngineBoots < 0) {
_LocalEngine.EngineBoots = 1; // The EngineBoots value 0 has a special meaning in discovering the time of the engine, so we start with 1.
}
}
}
#endif
}
}
/*********************************************************************
*
* Global functions
*
**********************************************************************
*/
/*********************************************************************
*
* MainTask()
*
* Function description
* Main sample starting point when running one individual sample.
*
* Additional information
* Runs the sample in a task of its own to decouple it from not
* knowing the required task stack size of this sample when
* starting from main() . This task typically is terminated once
* it has fulfilled its purpose.
*
* This routine initializes everything that might be common with
* other samples of the same kind. These initializations can then
* be skipped by not starting from MainTask() but APP_MainTask()
* instead.
*/
void MainTask(void) {
int IFaceId;
//
// Initialize the IP stack
//
IP_Init();
//
// Start TCP/IP task
//
OS_CREATETASK(&_IPTCB, "IP_Task", IP_Task, APP_TASK_PRIO_IP_TASK , _IPStack);
#if USE_RX_TASK
OS_CREATETASK(&_IPRxTCB, "IP_RxTask", IP_RxTask, APP_TASK_PRIO_IP_RXTASK, _IPRxStack); // Start the IP_RxTask, optional.
#endif
IP_AddStateChangeHook(&_StateChangeHook, _OnStateChange); // Register hook to be notified on disconnects.
IFaceId = IP_INFO_GetNumInterfaces() - 1; // Get the last registered interface ID as this is most likely the interface we want to use in this sample.
IP_Connect(IFaceId); // Connect the interface if necessary.
//
// IPv4 address configured ?
//
while (IP_IFaceIsReadyEx(IFaceId) == 0) {
BSP_ToggleLED(0);
OS_Delay(200);
}
BSP_ClrLED(0);
//
// Start the sample itself.
// The MAINTASK priority is used for this routine as from the outside this is the only
// priority we know for sure. When including the sample into another C-module this should
// also use the MAINTASK prio. The sample can then use one of the MAINTASK-n priorities
// if the main task routine of the sample shall not have the highest priorirty.
//
OS_CREATETASK(&APP_MainTCB, "APP_MainTask", APP_MainTask, APP_TASK_PRIO_MAINTASK, APP_MainStack);
OS_TASK_Terminate(NULL);
}
/*************************** End of file ****************************/