NetBurner 3.5.7
PDF Version
Custom MIB

Example Path: examples/snmp/CustomMIB

SNMP Custom MIB Example

Overview

This example demonstrates SNMP (Simple Network Management Protocol) agent functionality on a NetBurner device. It implements a basic SNMP agent with three custom enterprise MIB variables and provides interactive command interfaces via serial console and Telnet.

Note
For a detailed guide on creating your own custom SNMP MIBs and traps, see the SNMP section in the Programmers Guide

Features

  • SNMP agent listening on UDP port 161 for queries
  • Three custom read/write MIB variables under enterprise OID 8174 (NetBurner)
  • SNMP enterprise trap sending with custom variable bindings
  • MIB-II standard variables (system description, uptime, etc.)
  • Interactive command processor accessible via serial console and Telnet
  • User authentication for command sessions
  • HTTP web UI on port 80 showing live SNMP values, with a "Send Test Trap" button that fires the same SnmpTrapWithData() call as the 'T' Telnet command
  • Persistent SNMP configuration stored in flash (community strings, trap destination) managed through the NetBurner config_obj system

Network Ports

Port 23 (TCP) - Telnet command interface
Port 80 (TCP) - HTTP web server
Port 161 (UDP) - SNMP agent (inbound queries)
Port 162 (UDP) - SNMP trap destination (outbound)

Custom MIB Variables

The example defines three enterprise MIB variables under OID 1.3.6.1.4.1.8174 (NetBurner enterprise number). These are defined in Nburn_Cname_Mib.txt and implemented in Nburn_Cname_Mib.cpp.

OID Name Type Access
1.3.6.1.4.1.8174.1.0 READCOMMUNITY String Read-Write
1.3.6.1.4.1.8174.2.0 WRITECOMMUNITY String Read-Write
1.3.6.1.4.1.8174.3.0 TRAPDESTINATION IpAddress Read-Write

These variables allow remote SNMP managers to read and modify the device's SNMP community strings and trap destination address. Changes are persisted to flash storage via SaveConfigToStorage().

The "Read-Write" column above is the MIB-level access – what the variable is conceptually capable of. At runtime each request is also filtered by an access mask tied to which community string the manager presented; see "SNMP Communities and Access Masks" below for what that means in practice and why WRITECOMMUNITY is gated differently than the other two.

SNMP Communities and Access Masks

SNMPv1/v2c authenticates each request with a community string – a plaintext shared secret sent with every PDU. The agent recognizes two of them, configured in TheSnmpConfig and persisted to flash:

  • The read community (the -c value used by snmpget / snmpwalk) is the "read-only password". It authorizes Get / GetNext requests.
  • The write community is the higher-privileged "read-write password" used by snmpset. A manager that knows the write community can do everything the read community can do, plus issue Sets.

When a request arrives, the agent decodes the community string into a bitmask (see DefaultCommunityDecode in nbrtos/source/snmp/snmp.cpp):

read community matches -> READ_COMMUNITY_MASK
int read(int fd, char *buf, int nbytes)
Read data from a file descriptor (fd).
int write(int fd, const char *buf, int nbytes)
Write data to the stream associated with a file descriptor (fd). Can be used to write data to stdio,...
#define READ_COMMUNITY_MASK
Bitmask indicating read community access.
Definition snmp.h:312
#define WRITE_COMMUNITY_MASK
Bitmask indicating write community access.
Definition snmp.h:313

That bitmask is then ANDed against the access mask each leaf was registered with. The two masks therefore mean:

Mask A leaf with this mask is reachable by ...
READ_COMMUNITY_MASK the read community OR the write community.
WRITE_COMMUNITY_MASK only the write community.

The usual pairing is SNMPREADFUNC(..., READ_COMMUNITY_MASK) plus SNMPWRITEFUNC(..., WRITE_COMMUNITY_MASK) so that read-community clients can Get the value but only write-community clients can Set it. READCOMMUNITY and TRAPDESTINATION follow that pattern. When the mask rejects a request, the agent does not return a permission error – it behaves as if the OID is not there, and net-snmp surfaces that as No Such Object available on this agent at this OID.

Why WRITECOMMUNITY is the exception

WRITECOMMUNITY is the one leaf in this example whose read side is also gated on WRITE_COMMUNITY_MASK:

SNMPREADFUNC ( WRITECOMMUNITY, "1.3.6.1.4.1.8174.2.0",
ASN_typeString, ReadFuncWRITECOMMUNITY,
WRITE_COMMUNITY_MASK ); // read also requires write community
SNMPWRITEFUNC( WRITECOMMUNITY, "1.3.6.1.4.1.8174.2.0",
ASN_typeString, WriteFuncWRITECOMMUNITY,
#define SNMPREADFUNC(w, x, y, z, q)
Register a read callback for one SNMP scalar at file scope.
Definition snmp_table.h:444
#define SNMPWRITEFUNC(w, x, y, z, q)
Register a write callback for one SNMP scalar at file scope.
Definition snmp_table.h:471
#define ASN_typeString
Use with snmp_typeString.
Definition snmp_table.h:129

The reason is privilege escalation. The value of WRITECOMMUNITY is the credential a manager needs to perform Sets. If a read-community client could Get this leaf, they would learn the write community and promote themselves from read-only to read-write – the read community would effectively confer write access. Requiring the write community on the read side ensures only a manager who already holds the write credential can read it back, so they learn nothing they did not already know. (The trade-off is the misleading No Such Object message described below; that is acceptable, leaking the password is not.)

When and how to read or write WRITECOMMUNITY

In every case below, <write-community> must be the current write community. The example ships with both communities defaulted to public, so the asymmetry is invisible until you change WRITECOMMUNITY to a different value.

  • Read it – verify the current write community:

    snmpget -v2c -c <write-community> 192.168.1.100 .1.3.6.1.4.1.8174.2.0

    Using the read community here returns No Such Object available on this agent at this OID. That is the access mask rejecting the request, not a missing OID – expected behaviour, not a bug.

  • Change it – rotate the write community:

    snmpset -v2c -c <write-community> 192.168.1.100 .1.3.6.1.4.1.8174.2.0 s "newWriteCommunity"

    Every subsequent Set, and every subsequent Get of WRITECOMMUNITY.0, must use the new value in -c. If you forget it there is no SNMP path back in – recover via the local web UI, the serial console, or a factory reset of the device's stored config.

Build Instructions

SNMP support is enabled by the predef-overload.h file located at:

overload/nbrtos/include/predef-overload.h

which contains:

#define ENABLE_SNMP (1)

Project Files

makefile - Build configuration (lists every .cpp under
src/ plus the comphtml rule that bakes html/
into src/htmldata.cpp)
src/main.cpp - Application entry point. Globals (AppName,
SYSDESC, SYSOID, TheSnmpServer) plus a
~10-line UserMain that just brings the stack
up and calls StartCommandLine()
src/commands.h
src/commands.cpp - Telnet/serial CLI. Five Process* callbacks
(Auth, Connect, Command, Prompt, Disconnect)
with file-scope linkage, plus the public
StartCommandLine() that wires them up and
starts the command-processor task
src/trap.h
src/trap.cpp - SNMP trap PDU builder + SendTestTrap().
Called from both the 'T' command in
commands.cpp and the web POST handler in
web.cpp
src/web.cpp - HTTP UI (CPPCALL handlers for live values
and the trap form's POST callback).
file scope
src/htmlvar.h - Header included by the generated
src/htmldata.cpp; declares (or pulls in via
<nbrtos.h>) every symbol referenced by a
<!--VARIABLE ... --> tag in html/
src/Nburn_Cname_Mib.cpp - Custom MIB scalar registrations and the
read/write callbacks for the three
enterprise OIDs
mib/Nburn_Cname_Mib.txt - SMI MIB definition (RFC 1155/1212 format)
html/index.html - The web UI page. Uses CPPCALL and VARIABLE
tags; comphtml turns it into htmldata.cpp
at build time
overload/nbrtos/include/
predef-overload.h - Build flag to enable SNMP support
Implements the HtmlPostVariableListHandler class as a function pointer callback for HTTP POST submiss...
Definition httppost.h:131
const char * AppName
EFFS Multiple Partitions Example.
Definition aes/src/main.cpp:10
const char * SYSDESC
Application-defined system description string for MIB-II sysDescr.
Definition snmp/CustomMIB/src/main.cpp:44
const char * SYSOID
Application-defined system object identifier string for MIB-II sysObjectID.
Definition snmp/CustomMIB/src/main.cpp:45

How It Works

  1. Initialization (main.cpp - UserMain)

    The application starts in UserMain() and performs the following:

    The SNMP agent is started automatically by the global SnmpServlet object (TheSnmpServer) which listens on UDP port 161 – merely declaring it at file scope is enough to bring the agent online.

  2. Command Processor (commands.cpp)

    The NetBurner command processor provides an interactive CLI accessible through both the serial console and Telnet (port 23). All five callbacks are file-scope inside commands.cpp; main.cpp never sees their names. A single public function – StartCommandLine() in commands.h – wires them up and starts the task.

    • ProcessAuth() - Validates user credentials. For demonstration, any username/password pair is accepted as long as they are not identical.
    • ProcessConnect() - Called when a new session starts. Displays a welcome message and determines the prompt based on connection type (Serial0, Serial1, or Telnet).
    • ProcessCommand() - Handles user commands (see Commands below). The 'T' branch calls SendTestTrap() from trap.h.
    • ProcessPrompt() - Displays the "NB:<type>>" prompt.
    • ProcessDisconnect() - Displays a message when a session ends.

    Sessions time out after 60 seconds of inactivity.

  3. Custom MIB Variables (Nburn_Cname_Mib.cpp)

    Each MIB variable has a read function and a write function registered with the SNMP framework via SNMPREADFUNC and SNMPWRITEFUNC macros.

    Read functions return the current value from TheSnmpConfig, the global SNMP configuration object that provides persistent storage.

    Write functions validate the new value (in the bTest pass) and then apply it to TheSnmpConfig and call SaveConfigToStorage() to persist the change to flash.

  4. SNMP Traps (trap.cpp + trap.h)

    The SendTestTrap() function sends an enterprise-specific SNMP trap (trap type 6, specific code 1) to the configured trap destination. The internal file-scope TrapVarFunction() callback adds two custom variable bindings containing test messages to the trap PDU using ASN.1 encoding.

    trap.h is included by both commands.cpp (for the 'T' command) and web.cpp (for the trap-button POST handler) – one definition, two call sites, no forward declarations leaking through main.cpp.

  5. Web UI (web.cpp + html/index.html + htmlvar.h)

    The HTTP server (started by StartHttp() in UserMain) serves html/index.html, which displays:

    • Live values of sysDescr, sysUpTime, READCOMMUNITY, WRITECOMMUNITY, and TRAPDESTINATION pulled from TheSnmpConfig at request time
    • A "Send Test Trap" button that POSTs to /trap, invoking the same SendTestTrap() the 'T' Telnet command uses, and reports a running count of traps sent and the uptime of the most recent one
    • A static reference table of the custom MIB layout

    Mechanics:

    • <!--CPPCALL FunctionName --> in the HTML calls a C++ function named FunctionName(int sock, PCSTR url) in web.cpp; the function writes its output to the socket via fdprintf().
    • <!--VARIABLE Secs --> evaluates the Secs global at request time; the symbol must be visible to the generated htmldata.cpp via htmlvar.h (which includes <nbrtos.h>).
    • The "Send Test Trap" form posts to action="trap"; web.cpp registers a HtmlPostVariableListCallback("trap*", ...) at file scope, whose handler fires SendTestTrap() on eEndOfPost and 302-redirects back to /index.html.

    At build time, the makefile's comphtml html -osrc/htmldata.cpp step walks html/ and converts every file (HTML, images, CSS) into a single src/htmldata.cpp that is compiled and linked into the firmware – there is no filesystem at runtime.

Adding Your Own Scalar Variables

Most applications want SNMP scalar variables that a manager can Get and Set. The three entries in Nburn_Cname_Mib.cpp (READCOMMUNITY, WRITECOMMUNITY, TRAPDESTINATION) are exactly this pattern – a read-function plus an optional write-function, registered with the SNMPREADFUNC / SNMPWRITEFUNC macros. Delete those three and put your own in their place.

A minimal example – one integer voltage reading and one on/off flag

Replace the contents of src/Nburn_Cname_Mib.cpp with the following (keep everything else in the project as-is):

#include <stdio.h>
#include <nbrtos.h>
#include <snmp.h>
/* --- Your actual device variables ---
In real code these would be updated by whatever task reads the ADC, etc. */
static int g_voltage_mv = 3300; // read-only: voltage in millivolts
static int g_output_on = 0; // read-write: 0 = off, 1 = on
/* --- Read callbacks --- */
snmp_typeInteger32 ReadVoltage() { return g_voltage_mv; }
snmp_typeInteger32 ReadOutput() { return g_output_on; }
/* --- Write callback ---
bTest != 0 means "validate only, don't commit".
bTest == 0 means "actually apply the value".
Return SNMP_SET_OK or SNMP_SET_FAIL. */
int WriteOutput(int var, int bTest)
{
if (bTest)
{
if (var != 0 && var != 1) return SNMP_SET_FAIL; // reject anything but 0/1
return SNMP_SET_OK;
}
g_output_on = var;
// TODO: drive your GPIO / relay here
return SNMP_SET_OK;
}
/* --- Register them with the agent ---
Pick any OIDs under your enterprise subtree. Scalars end in ".0". */
SNMPREADFUNC (VOLTAGE, "1.3.6.1.4.1.8174.10.1.0", ASN_typeInteger32, ReadVoltage, READ_COMMUNITY_MASK);
SNMPREADFUNC (OUTPUT, "1.3.6.1.4.1.8174.10.2.0", ASN_typeInteger32, ReadOutput, READ_COMMUNITY_MASK);
SNMPWRITEFUNC(OUTPUT, "1.3.6.1.4.1.8174.10.2.0", ASN_typeInteger32, WriteOutput, WRITE_COMMUNITY_MASK);
#define SNMP_SET_OK
Value is acceptable / applied successfully.
Definition snmp_table.h:90
#define ASN_typeInteger32
Use with snmp_typeInteger32.
Definition snmp_table.h:139
#define SNMP_SET_FAIL
Value is invalid or the set cannot be applied.
Definition snmp_table.h:89

Build and load. In iReasoning (or any SNMP manager), do a Get on 1.3.6.1.4.1.8174.10.1.0 – you should see 3300. A Get on ...10.2.0 returns 0. Set ...10.2.0 to 1, then Get it again to confirm. A Walk of 1.3.6.1.4.1.8174.10 will show both.

Types you're likely to use

All of these are just typedefs – pick the one that matches the value semantics:

Macro type C type Use for
ASN_typeInteger32 int signed integers, on/off flags
ASN_typeUnsigned unsigned int unsigned readings (0..2^32-1)
ASN_typeGauge int instantaneous values that go up/down
ASN_typeCounter unsigned int monotonically-increasing counts
ASN_typeString const char * strings
ASN_typeIpAddr unsigned int IPv4 address

The rule is: the read function's return type and the write function's parameter type must match the typedef that corresponds to the macro. Example pairings live at the top of snmp/include/snmp_table.h if you want to see them all.

Commands

Connect via serial console or Telnet to port 23. Log in with any username and password that are not identical (e.g., user: "hello", pass: "world").

D - Dump the full SNMP MIB tree to the console
T - Send a test SNMP enterprise trap to the configured destination
logout - Close the current session
(other) - Display command help

Testing with the net-snmp Command-Line Tools

The NetBurner SDK ships a portable net-snmp 5.9.1 distribution at:

C:\nburn\snmp\net-snmp\

containing snmpget.exe, snmpset.exe, snmpwalk.exe, snmptable.exe, snmptranslate.exe, etc. The standard MIBs (RFC1155-SMI, RFC1213-MIB, SNMPv2-SMI, SNMPv2-TC, ...) live alongside the per-example MIBs at:

C:\nburn\snmp\mibs\

If you copy this example's mib\Nburn_Cname_Mib.txt into that mibs folder, the tools will resolve symbolic names like READCOMMUNITY.0, WRITECOMMUNITY.0, and TRAPDESTINATION.0 from NBURNSAMPLE-MIB. Without the copy you can still use full numeric OIDs and everything works.

All examples below use the example IP address 192.168.1.100. Substitute your NetBurner's actual IP (run nbdiscovery if you do not know it).

One-time setup so you can copy/paste cleanly

set PATH=C:\nburn\snmp\net-snmp;PATH% set MIBDIRS=C:\nburn\snmp\mibs set MIBS=ALL

Equivalent in PowerShell:

$env:PATH = "C:\nburn\snmp\net-snmp;$env:PATH" $env:MIBDIRS = "C:\nburn\snmp\mibs" $env:MIBS = "ALL"

In a Bash shell (Git Bash / MSYS) use Unix-style paths:

export PATH="/c/nburn/snmp/net-snmp:$PATH" export MIBDIRS="/c/nburn/snmp/mibs" export MIBS="ALL"

After that the -M / -m flags below can be omitted. All examples include them explicitly so they work even without the env vars.

snmpget – read one or more values

By symbolic name (after copying Nburn_Cname_Mib.txt into the mibs folder):

snmpget -v2c -c public -M C:\nburn\snmp\mibs -m ALL 192.168.1.100 READCOMMUNITY.0 WRITECOMMUNITY.0 TRAPDESTINATION.0

Expected output:

NBURNSAMPLE-MIBREADCOMMUNITY.0 = STRING: "public" NBURNSAMPLE-MIBWRITECOMMUNITY.0 = STRING: "public" NBURNSAMPLE-MIBTRAPDESTINATION.0 = IpAddress: 0.0.0.0

By numeric OID (no MIB needed):

snmpget -v2c -c public 192.168.1.100 .1.3.6.1.4.1.8174.1.0 .1.3.6.1.4.1.8174.2.0 .1.3.6.1.4.1.8174.3.0

Standard MIB-II values served by the NetBurner agent:

snmpget -v2c -c public -M C:\nburn\snmp\mibs -m ALL 192.168.1.100 sysDescr.0 sysUpTime.0 sysObjectID.0

snmpset – write to a read-write variable

Type letter goes between the OID and the value: s = string, i = integer, a = IpAddress, u = unsigned, c = counter, t = TimeTicks, o = OID, x = hex string

Set a community string (string type, letter s):

snmpset -v2c -c public -M C:\nburn\snmp\mibs -m ALL 192.168.1.100 READCOMMUNITY.0 s "newcommunity"

or equivalently with a numeric OID:

snmpset -v2c -c public 192.168.1.100 .1.3.6.1.4.1.8174.1.0 s "newcommunity"

Set the trap destination (IpAddress type, letter a):

snmpset -v2c -c public -M C:\nburn\snmp\mibs -m ALL 192.168.1.100 TRAPDESTINATION.0 a 192.168.1.7

verify the change persisted to flash:

snmpget -v2c -c public -M C:\nburn\snmp\mibs -m ALL 192.168.1.100 TRAPDESTINATION.0

Important: writes go through the write community, not the read community. This example ships with both defaulted to public, so -c public works either way. As soon as you change WRITECOMMUNITY.0 to something else, all later snmpset commands must use that new community string in -c.

snmpwalk – enumerate a subtree

Walk just the NetBurner enterprise tree:

snmpwalk -v2c -c public -M C:\nburn\snmp\mibs -m ALL 192.168.1.100 .1.3.6.1.4.1.8174

Walk standard MIB-II system + interfaces:

snmpwalk -v2c -c public -M C:\nburn\snmp\mibs -m ALL 192.168.1.100 system snmpwalk -v2c -c public -M C:\nburn\snmp\mibs -m ALL 192.168.1.100 interfaces

snmptranslate – offline OID name/number lookup

Useful when something does not respond and you want to confirm the MIB is loaded correctly:

snmptranslate -M C:\nburn\snmp\mibs -m ALL -On READCOMMUNITY.0 snmptranslate -M C:\nburn\snmp\mibs -m ALL .1.3.6.1.4.1.8174.1.0

Troubleshooting

Cannot find module (NBURNSAMPLE-MIB) Copy this example's mib\Nburn_Cname_Mib.txt to C:\nburn\snmp\mibs\ and retry.

Timeout Verify with ping 192.168.1.100 and confirm the device is on the same subnet. SNMP listens on UDP/161; check Windows Firewall.

Error in packet, Reason: (readOnly) You used the read community on a write operation, or you used the old WRITECOMMUNITY value after changing it. Use the current value of WRITECOMMUNITY in -c for snmpset.

No Such Object available on this agent at this OID The OID is not registered, or the access mask rejected the request under the community you used. Check what community the leaf was registered with in the SNMPREADFUNC/SNMPWRITEFUNC macro.

SNMP System Identifiers

sysDescr.0 : "NetBurner SNMP Test application" sysObjectID.0 : 1.3.6.1.4.1.8174.2.40