ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
snmp_agent.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file snmp_agent.h
6 * @brief The command responder: community-framed messages, PDU processing, and a fixed MIB.
7 *
8 * The agent answers management requests against a fixed table of managed objects. Two framings
9 * reach the same table:
10 *
11 * - **SNMPv1**, RFC 1157 sec 4: `Message ::= SEQUENCE { version version-1(0), community OCTET
12 * STRING, data ANY }`. RFC 1157 sec 3.2.5 makes the community the whole of authentication: a
13 * message is authentic when it belongs to the community it names. RFC 1157 sec 4.1 says an
14 * entity that fails authentication notes the failure and discards the datagram, so an unknown
15 * community is answered with nothing at all.
16 * - **SNMPv2c**, RFC 1901 sec 3: the same wrapper with version(1), carrying the RFC 3416 PDUs.
17 *
18 * PDU processing is RFC 3416 sec 4.2: the GetRequest-PDU (sec 4.2.1), the GetNextRequest-PDU
19 * (sec 4.2.2), the GetBulkRequest-PDU (sec 4.2.3) and the SetRequest-PDU (sec 4.2.5), each
20 * answered by a Response-PDU (sec 4.2.4). The GetBulkRequest-PDU and the per-binding exceptions
21 * noSuchObject, noSuchInstance and endOfMibView belong to v2c and v3; v1 reports through
22 * error-status and error-index instead (RFC 1157 sec 4.1.1).
23 *
24 * The MIB is a fixed table of SNMP_MAX_MIB_ENTRIES object instances, in BSS. A value is either
25 * held in the entry or fetched through a getter, as sysUpTime.0 is. String and OBJECT IDENTIFIER
26 * values are referenced by pointer and must outlive the agent.
27 *
28 * Message processing is pure: request octets in, response octets out, no socket and no heap, so
29 * it is unit-tested with no network stack under it. RFC 3417 sec 3.2 suggests command responders
30 * listen on UDP port 161, which @ref SnmpAgentNs::listen binds through the transport UDP service.
31 * SNMPv3 is the separate USM layer, reached through ::SnmpV3.
32 *
33 * @author Douglas Quigg (dstroy0)
34 * @date 2026
35 */
36
37#ifndef PROTOCORE_SNMP_AGENT_H
38#define PROTOCORE_SNMP_AGENT_H
39
40#include "protocore_config.h" // the entry point: protocore_types.h for the widths
41
42#if PROTOCORE_ENABLE_SNMP
43
45
47/** @brief The version field of the message wrapper, as it is encoded. */
48typedef enum PROTO_ENUM_PACKED
49{
50 SNMP_V1 = 0, ///< version-1(0), RFC 1157 sec 4
51 SNMP_V2C = 1, ///< version(1), RFC 1901 sec 3
52 SNMP_V3 = 3, ///< msgVersion, RFC 3412 sec 6
53} SnmpVersion;
54
55/** @brief error-status values (RFC 1157 sec 4.1.1 for 0 through 5, RFC 3416 sec 3 for the rest). */
56typedef enum PROTO_ENUM_PACKED
57{
58 SNMP_ERR_NO_ERROR = 0,
59 SNMP_ERR_TOO_BIG = 1,
60 SNMP_ERR_NO_SUCH_NAME = 2,
61 SNMP_ERR_BAD_VALUE = 3,
62 SNMP_ERR_READ_ONLY = 4,
63 SNMP_ERR_GEN_ERR = 5,
64 SNMP_ERR_NO_ACCESS = 6,
65 SNMP_ERR_WRONG_TYPE = 7,
66 SNMP_ERR_NOT_WRITABLE = 17,
67} SnmpErr;
68
69/**
70 * @brief The value half of a variable-binding (RFC 3416 sec 3).
71 *
72 * Only the field matching @ref type carries anything. String and OBJECT IDENTIFIER values are
73 * referenced, not copied, so they must stay valid.
74 */
75typedef struct
76{
77 uint8_t type; ///< the value's tag: an ASN.1 simple type, an SMIv2 application-wide type
78 ///< (RFC 2578 sec 7.1), or a VarBind exception marker
79 long ival; ///< the INTEGER value
80 uint32_t uval; ///< the TimeTicks, Counter32, Gauge32 or IpAddress value
81 const char *str; ///< the OCTET STRING octets, not owned
82 size_t str_len; ///< how many
83 const uint32_t *oid; ///< the OBJECT IDENTIFIER subidentifiers, not owned
84 size_t oid_len; ///< how many
85} SnmpValue;
86
87/** @brief Read a dynamic object's value: fill @p out and return true, or false for noSuchInstance. */
88typedef proto_bool (*SnmpGetFn)(SnmpValue *out);
89/** @brief Write a read-write object (RFC 2578 sec 7.3): true on success, false to reject the value. */
90typedef proto_bool (*SnmpSetFn)(const SnmpValue *in);
91
92/** @brief RFC 1157 sec 3.2.5: the communities a message is authenticated against. */
93typedef struct
94{
95 const char *ro; ///< the community that authorizes reads; NULL keeps the built-in default
96 const char *rw; ///< the community that authorizes a SetRequest-PDU; NULL or empty refuses every write
97} SnmpCommunityArgs;
98
99/** @brief RFC 2578 sec 7: one managed object instance and how its value is reached. */
100typedef struct
101{
102 const uint32_t *oid; ///< the instance name, as subidentifiers
103 size_t oid_len; ///< how many, at least 2
104 uint8_t type; ///< a dynamic object's value tag (RFC 2578 sec 7.1)
105 const char *text; ///< the OCTET STRING value a static registration takes, referenced not copied
106 long ival; ///< the INTEGER value a static registration takes
107 SnmpGetFn getter; ///< what a dynamic object's value is read through
108 SnmpSetFn setter; ///< what a write reaches; NULL leaves the object read-only (RFC 2578 sec 7.3)
109} SnmpObjectArgs;
110
111/** @brief RFC 3418 sec 2: the system group under 1.3.6.1.2.1.1. */
112typedef struct
113{
114 const char *descr; ///< sysDescr.0
115 const char *contact; ///< sysContact.0
116 const char *name; ///< sysName.0
117 const char *location; ///< sysLocation.0
118 long services; ///< sysServices.0, the layer bitmask
119} SnmpSystemArgs;
120
121/** @brief RFC 3417 sec 3.1: the serialized message read, and the one written back. */
122typedef struct
123{
124 const uint8_t *req; ///< the received message octets
125 size_t req_len; ///< how many
126 uint8_t *resp; ///< where the response message is written
127 size_t resp_cap; ///< how many octets that holds
128} SnmpMsgArgs;
129
130/** @brief RFC 3416 sec 4.2: the request PDU dispatched, and the Response-PDU written. */
131typedef struct
132{
133 const uint8_t *req; ///< one complete request-PDU TLV
134 size_t req_len; ///< its length in octets
135 uint8_t *out; ///< where the Response-PDU TLV is written
136 size_t out_cap; ///< how many octets that holds
137 proto_bool allow_write; ///< a SetRequest-PDU is authorized (RFC 3416 sec 4.2.5)
138 proto_bool v2c; ///< report per-binding exceptions rather than v1 error-status
139} SnmpPduArgs;
140
141/**
142 * @brief The command responder: the MIB, the PDU processing, and the message framing.
143 *
144 * A caller sets the members a call takes, invokes it through ::SnmpAgent, and reads the outcome
145 * off the same handle.
146 *
147 * @var SnmpAgentNs::port the UDP port a listen binds, 161 by convention (RFC 3417 sec 3.2)
148 * @var SnmpAgentNs::community the communities a message is authenticated against
149 * @var SnmpAgentNs::object the managed object a registration binds
150 * @var SnmpAgentNs::system the system group values (RFC 3418 sec 2)
151 * @var SnmpAgentNs::msg the message read and the one written back
152 * @var SnmpAgentNs::pdu the PDU dispatched and the Response-PDU written
153 * @var SnmpAgentNs::ok a registration's true/false outcome: the table had room
154 * @var SnmpAgentNs::n octets written, 0 to send nothing
155 * @var SnmpAgentNs::init empty the MIB and take the read-only community
156 * @var SnmpAgentNs::set_rw_community take the community that authorizes a SetRequest-PDU
157 * @var SnmpAgentNs::set_system register the system group (RFC 3418 sec 2)
158 * @var SnmpAgentNs::add_string register an object whose value is an OCTET STRING
159 * @var SnmpAgentNs::add_integer register an object whose value is an INTEGER
160 * @var SnmpAgentNs::add_dynamic register an object whose value is read through a getter
161 * @var SnmpAgentNs::dispatch_pdu run one request PDU against the MIB and write a Response-PDU
162 * @var SnmpAgentNs::process decode one message, dispatch it, and frame the response message
163 * @var SnmpAgentNs::listen answer requests arriving on @c port
164 */
165typedef struct
166{
167 uint16_t port; ///< the UDP port a listen binds
168 SnmpCommunityArgs community; ///< what authenticates a message (RFC 1157 sec 3.2.5)
169 SnmpObjectArgs object; ///< what a registration binds (RFC 2578 sec 7)
170 SnmpSystemArgs system; ///< the system group values (RFC 3418 sec 2)
171 SnmpMsgArgs msg; ///< the message pair (RFC 3417 sec 3.1)
172 SnmpPduArgs pdu; ///< the PDU pair (RFC 3416 sec 4.2)
173 proto_bool ok;
174 size_t n;
175} SnmpAgentVars;
176
177/** @brief The operands and the outcome. */
178extern SnmpAgentVars SnmpAgentV;
179
180/** @brief The entries. */
181typedef struct
182{
183 void (*const init)(uint8_t *work);
184 void (*const set_rw_community)(uint8_t *work);
185 void (*const set_system)(uint8_t *work);
186 void (*const add_string)(uint8_t *work);
187 void (*const add_integer)(uint8_t *work);
188 void (*const add_dynamic)(uint8_t *work);
189 void (*const dispatch_pdu)(uint8_t *work);
190 void (*const process)(uint8_t *work);
191 void (*const listen)(uint8_t *work);
192} SnmpAgentNs;
193
194// What the table binds, defined once in the .c and taking one parameter each: everything
195// else an entry needs is an operand in SnmpAgentV or a region of the borrow at a fixed offset.
196void protocore_snmp_agent_init(uint8_t *work);
197void protocore_snmp_agent_set_rw_community(uint8_t *work);
198void protocore_snmp_agent_set_system(uint8_t *work);
199void protocore_snmp_agent_add_string(uint8_t *work);
200void protocore_snmp_agent_add_integer(uint8_t *work);
201void protocore_snmp_agent_add_dynamic(uint8_t *work);
202void protocore_snmp_agent_dispatch_pdu(uint8_t *work);
203void protocore_snmp_agent_process(uint8_t *work);
204void protocore_snmp_agent_listen(uint8_t *work);
205
206// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
207// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
208// `SnmpAgent.init(work)` resolves to a named function and becomes a DIRECT call. An extern table
209// leaves the call indirect and the symbol live at every level, -O2 -flto included.
210static const SnmpAgentNs SnmpAgent __attribute__((unused)) = {
211 .init = protocore_snmp_agent_init,
212 .set_rw_community = protocore_snmp_agent_set_rw_community,
213 .set_system = protocore_snmp_agent_set_system,
214 .add_string = protocore_snmp_agent_add_string,
215 .add_integer = protocore_snmp_agent_add_integer,
216 .add_dynamic = protocore_snmp_agent_add_dynamic,
217 .dispatch_pdu = protocore_snmp_agent_dispatch_pdu,
218 .process = protocore_snmp_agent_process,
219 .listen = protocore_snmp_agent_listen,
220};
221
222/**
223 * @brief The PROTOCORE_SNMP_AGENT_BORROW bytes this module's state lives in.
224 *
225 * Stated beside the namespace rather than on it: an entry takes a borrow, and this is where
226 * that borrow comes from. Taken once from the end of the pool, which no mark and no release
227 * walks, so the state lasts the life of the program.
228 *
229 * @return the span.
230 */
231uint8_t *protocore_snmp_agent_span(void);
232
234
235#endif // PROTOCORE_ENABLE_SNMP
236
237#endif // PROTOCORE_SNMP_AGENT_H
PROTO_ENUM_PACKED
Application protocol spoken on a listener port or connection slot.
The SNMP serialization: ASN.1 Basic Encoding Rules over a caller buffer.
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97