Skip to content

Commit

Permalink
Update example and readme
Browse files Browse the repository at this point in the history
  • Loading branch information
0neblock committed Nov 15, 2021
1 parent 7ac443f commit c739b36
Show file tree
Hide file tree
Showing 3 changed files with 197 additions and 22 deletions.
166 changes: 161 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,32 @@
# SNMP_Agent v2
SNMP Agent v2c built with Arduino
# SNMP_Agent
### (Previously Arduino_SNMP)

This is a fully-compliant SNMPv2c Agent built for Arduinos, but will work on any OS, providing API code is written for packet serialization (See tests/mock.cpp for an example)
SNMP Agent built with Arduino

This is a fully-compliant SNMPv2c Agent built for Arduino's, but will work on any OS, providing API code is written for packet serialization (See tests/mock.cpp for an example)

## Features
* Full SNMPv2c Data Type support:
* INTEGER `int`
* STRING `std::string` or `const char*`
* NULLTYPE
* OIDTYPE
* Complex data type support:
* NETWORK ADDRESS
* COUNTER32 `uint32_t`
* GUAGE32 `uint32_t`
* TIMESTAMP `uint32_t`
* OPAQUE `uint8_t*`
* CONTER64 `uint64_t`
* SNMP PDU Support
* GetRequest
* GetNextRequest
* GetResponse (For SNMPv2c INFORM Responses only for now)
* SetRequest
* SNMPv2 Trap
* GetBulkRequest
* InformRequest
* SNMPv2 Trap

It was designed and tested around an ESP32, but will work with any Arduino-based devied that has a UDP object available.

Expand All @@ -10,8 +35,139 @@ The example goes into detail around how to use, or look at `src/SNMP_Agent.h` fo
If you're coming from v1, most, but not all APIs are drop-in replaceable.
Some of the API's, especially around strings have changed. Look in `SNMP_Agent.h` for details.

It you need a STRING OID that can be written to/updated, be very sure that you need to update it, because you will be dealingwith raw pointers. It's safer to use `addReadOnlyStaticStringHandler()` instead.
It you need a STRING OID that can be written to/updated, be very sure that you need to update it, because you will be dealing with raw pointers. It's safer to use `addReadOnlyStaticStringHandler()` instead.

It does not support the Arduino `String` type, only the C++ standard `std::string` type.

## Getting Started

To setup a simple SNMP Agent, include the required libraries and declare an instance of the SNMPAgent class;

```
#include <SNMP_Agent.h>
/* Can declare read-write, or both read-only and read-write community strings */
SNMPAgent snmp("public", "private");
```

Depending on what arduino you are using, you will have to setup the wifi/internet conection for the device.
For ESP32, you can use `WiFi.begin()`, you will then need to supply a `UDP` object to the snmp library.

```
#include <WiFi.h>
#include <WiFiUdp.h>
WiFiUDP udp;
... later in setup()
WiFi.begin(ssid, password);
// Give snmp a pointer to the UDP object
snmp.setUDP(&udp);
// Add OID Handlers (see below)
...
snmp.begin();
... later in loop()
snmp.loop();
```

### Setting OID callbacks

If you want the Arduino to response to an SNMP server at some specified OIDs, you need to implement a `ValueCallback` for each OID, attached to a variable to respond with.
Whenever an OID is requested by sn SNMP manager, the ValueCallback for that OID is found, and the latest value of that variable is used to respond.

For example, to respones to the OID ".1.3.6.1.4.1.5.0" with the number: 5.
```
int testNumber = 5;
snmp.addIntegerHandler(".1.3.6.1.4.1.5.0", &testNumber);
```

Yopu can enable SNMPSet requests, by setting `isSettable = true` as a parameter when adding the handler, for example:
```
int settableNumber = 0;
snmp.addIntegerHandler(".1.3.6.1.4.1.5.1", &settableNumber, true);
// snmpset -v 2c -c private <IP> 1.3.6.1.4.1.5.1 i 24
```

You can store the return value of the handler calls in a variable `ValueCallback*`, and use them later for things like SNMP Traps, or for removing the handler later.

Be sure to call `snmp.sortHandlers()` after adding any OID handlers, to ensure functions like SNMP Walk work correctly.


The full list of ValueCallback handlers you can specify can be found in `SNMP_Agent.h`

### SNMP Traps

You can send SNMP v1 traps, as well as SNMPv2 Trap and INFORMS with this library.

Therre are a few requirements in setting up a trap in order to comply with the SNMP RFC.

```
// Setup a trap object for later use, specify the SNMP version to use
// SNMP_VERSION_1 or SNMP_VERSION_2C
SNMPTrap* testTrap = new SNMPTrap("public", SNMP_VERSION_2C);
// SNMP Traps MUST send a timestamp value when sent. This timestamp doesn't have to be valid, but we have to create one anyway. The timestamp value is stored as "tens of milliseconds"
TimestampCallback* timestampCallback;
int tensOfMillisCounter = 0;
```
In `setup()`:
```
// The SNMP Trap spec requires an uptime counter to be sent along with the trap.
timestampCallback = (TimestampCallback*)snmp.addTimestampHandler(".1.3.6.1.2.1.1.3.0", &tensOfMillisCounter);
// Set UDP Object for trap to be sent on
testTrap->setUDP(&udp);
// OID of the trap
testTrap->setTrapOID(new OIDType(".1.3.6.1.2.1.33.2"));
// Specific Number of the trap
testTrap->setSpecificTrap(1);
// Set the uptime counter to use in the trap (required)
testTrap->setUptimeCallback(timestampCallback);
// Set some previously set OID Callbacks to send these values with the trap (optional)
testTrap->addOIDPointer(previouslySetValueCallback);
// Set our Source IP so the receiver knows where this is coming from
testTrap->setIP(WiFi.localIP());
// Set INFORM to be true or false (only works for SNMPV2 traps)
testTrap->setInform(true);
```

in `loop()`

```
// must be called as often as possible
snmp.loop();
// Update our timestamp value
tensOfMillisCounter = millis()/10;
// Send the trap to the specified IP address
IPAddress destinationIP = IPAddress(192, 168, 1, 243);
if(snmp.sendTrapTo(testTrap, destinationIP, true, 2, 5000) != INVALID_SNMP_REQUEST_ID){
Serial.println("Sent SNMP Trap");
} else {
Serial.println("Couldn't send SNMP Trap");
}
```

The `snmp.sendTrapTo()` values of `true, 2, 5000` indicate that if this is an INFORM request, it will try to send the INFORM up to 2 times, with a Timeout of 5000 milliseconds before it gives up, if it receives no response from the other end. The snmp.loop() will keep trying to resend the trap until the timeout or retry limit is reached.

There is currencly no mechanism to know (with code) if an SNMP INFORM request has been responded to. I hope to work on this in the future.

### SNMP Manager

It does not support the Arduino `String()` type, only the C++ standard `std::string` type
I am working on adding the functionality to act as an SNMP Server or Manager. In the meantime, if you need to do this, look at the library here: https://github.com/shortbloke/Arduino_SNMP_Manager

Pull requests/comments are welcome
47 changes: 33 additions & 14 deletions examples/ESP32_SNMP/ESP32_SNMP.ino
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,28 @@ const char* ssid = "SSID";
const char* password = "password";

WiFiUDP udp;
SNMPAgent snmp = SNMPAgent("public"); // Starts an SMMPAgent instance with the community string 'public'
// Starts an SMMPAgent instance with the read-only community string 'public', and read-write community string 'private
SNMPAgent snmp = SNMPAgent("public", "private");

// Numbers used to response to Get requests
int changingNumber = 1;
int settableNumber = 0;
int tensOfMillisCounter = 0;

// arbitrary data will be stored here to act as an OPAQUE data-type
uint8_t* stuff = 0;


// If we want to change the functionaality of an OID callback later, store them here.
ValueCallback* changingNumberOID;
ValueCallback* settableNumberOID;
TimestampCallback* timestampCallbackOID;

std::string staticString = "This value will never change";

// Setup an SNMPTrap for later use
SNMPTrap* settableNumberTrap = new SNMPTrap("public", SNMP_VERSION_2C);
char* changingString;

void setup(){
Serial.begin(115200);
Expand All @@ -42,6 +51,7 @@ void setup(){
snmp.setUDP(&udp);
snmp.begin();

// setup our OPAQUE data-type
stuff = (uint8_t*)malloc(4);
stuff[0] = 1;
stuff[1] = 2;
Expand All @@ -54,14 +64,20 @@ void setup(){
// Using your favourite snmp tool:
// snmpget -v 1 -c public <IP> 1.3.6.1.4.1.5.0

// you can accept SET commands with a pointer to an integer (or string)
// you can accept SET commands with a pointer to an integer
settableNumberOID = snmp.addIntegerHandler(".1.3.6.1.4.1.5.1", &settableNumber, true);

// snmpset -v 1 -c public <IP> 1.3.6.1.4.1.5.0 i 99

snmp.addIntegerHandler(".1.3.6.1.4.1.4.0", &changingNumber);

// More examples:
snmp.addIntegerHandler(".1.3.6.1.4.1.4.0", &changingNumber);
snmp.addOpaqueHandler(".1.3.6.1.4.1.5.9", stuff, 4, true);
snmp.addReadOnlyStaticStringHandler(".1.3.6.1.4.1.5.11", staticString);

// Setup read/write string
changingString = (char*)malloc(25 * sizeof(char));
snprintf(changingString, 25, "This string can be changed");
snmp.addReadWriteStringHandler(".1.3.6.1.4.1.5.12", &changingString, 25, true);


// Setup SNMP TRAP
Expand All @@ -72,26 +88,30 @@ void setup(){
settableNumberTrap->setTrapOID(new OIDType(".1.3.6.1.2.1.33.2")); // OID of the trap
settableNumberTrap->setSpecificTrap(1);

// Set the uptime counter to use in the trap
// Set the uptime counter to use in the trap (required)
settableNumberTrap->setUptimeCallback(timestampCallbackOID);

// Set some previously set OID Callbacks to send these values with the trap
// Set some previously set OID Callbacks to send these values with the trap (optional)
settableNumberTrap->addOIDPointer(changingNumberOID);
settableNumberTrap->addOIDPointer(settableNumberOID);

settableNumberTrap->setIP(WiFi.localIP()); // Set our Source IP

// Ensure to sortHandlers after adding/removing and OID callbacks - this makes snmpwalk work
snmp.sortHandlers();
}

void loop(){
snmp.loop(); // must be called as often as possible
if(snmp.setOccurred){
if(settableNumberOID->setOccurred){

Serial.printf("Number has been set to value: %i\n", settableNumber);
if(settableNumber%2 == 0){
// Sending an SNMPv2 INFORM (trap will be kept and re-sent until it is acknowledged by the IP address it was sent to)
settableNumberTrap->setVersion(SNMP_VERSION_2C);
settableNumberTrap->setInform(true);
settableNumberTrap->setInform(true); // set this to false and send using `settableNumberTrap->sendTo` to send it without the INFORM request
} else {
// Sending regular SNMPv1 trap
settableNumberTrap->setVersion(SNMP_VERSION_1);
settableNumberTrap->setInform(false);
}
Expand All @@ -100,17 +120,16 @@ void loop(){
// if(snmp.removeHandler(settableNumberOID)){
// Serial.println("Remove succesful");
// }
snmp.resetSetOccurred();
settableNumberOID->resetSetOccurred();

Serial.println("Lets send out a trap to indicate a changed value");

IPAddress destinationIP = IPAddress(192, 168, 1, 75);
if(snmp.sendTrapTo(settableNumberTrap, destinationIP, true, 2, 5000) != INVALID_SNMP_REQUEST_ID){ // Send the trap to the specified IP address
// Send the trap to the specified IP address
// If INFORM is set, snmp.loop(); needs to be called in order for the acknowledge mechanism to work.
IPAddress destinationIP = IPAddress(192, 168, 1, 243);
if(snmp.sendTrapTo(settableNumberTrap, destinationIP, true, 2, 5000) != INVALID_SNMP_REQUEST_ID){
Serial.println("Sent SNMP Trap");
} else {
Serial.println("Couldn't send SNMP Trap");
}
// Serial.println(ESP.getFreeHeap());
}
changingNumber++;
tensOfMillisCounter = millis()/10;
Expand Down
6 changes: 3 additions & 3 deletions library.properties
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
name=SNMP_Agent
version=2.0.1
version=2.0.2
author=Aidan Cyr <[email protected]>
maintainer=Aidan Cyr <[email protected]>
sentence=Arduino_SNMP: An SNMPv2c Agent for Arduino
paragraph=(Previously Arduino_SNMP)
sentence=SNMP Agent: An fully compliant SNMPv2c Agent for esp32 for acting as an SNMP client device.
paragraph=(Previously Arduino_SNMP) Can respond to SNMP GET and SET requests, and can send SNMP v1 or SNMP v2c TRAP and INFORM messages.
category=Communication
architectures=esp32
includes=SNMP_Agent.h, SNMPTrap.h
Expand Down

0 comments on commit c739b36

Please sign in to comment.