15#if DETWS_ENABLE_SNMP_V3
21static uint32_t snmp_uptime_cs()
23 return (uint32_t)(millis() / 10ULL);
26static uint32_t snmp_uptime_cs()
57static SnmpAgentCtx s_agent;
60static const uint32_t g_sys_object_id[] = {1, 3, 6, 1, 4, 1, 49374};
66static int oid_cmp(
const uint32_t *a,
size_t an,
const uint32_t *b,
size_t bn)
68 size_t n = an < bn ? an : bn;
69 for (
size_t i = 0; i < n; i++)
83static const SnmpMibEntry *mib_find_exact(
const SnmpAgentCtx &c,
const uint32_t *oid,
size_t n)
85 for (
size_t i = 0; i < c.mib_count; i++)
86 if (oid_cmp(c.mib[i].oid, c.mib[i].oid_len, oid, n) == 0)
92static const SnmpMibEntry *mib_find_next(
const SnmpAgentCtx &c,
const uint32_t *oid,
size_t n)
94 const SnmpMibEntry *best =
nullptr;
95 for (
size_t i = 0; i < c.mib_count; i++)
97 if (oid_cmp(c.mib[i].oid, c.mib[i].oid_len, oid, n) > 0)
99 if (!best || oid_cmp(c.mib[i].oid, c.mib[i].oid_len, best->oid, best->oid_len) < 0)
106static bool fetch_value(
const SnmpMibEntry *en, SnmpValue *out)
109 return en->getter(out);
120static bool mib_object_exists(
const SnmpAgentCtx &c,
const uint32_t *oid,
size_t n)
122 for (
size_t i = 0; i < c.mib_count; i++)
124 size_t objn = c.mib[i].oid_len - 1;
125 if (objn <= n && oid_cmp(c.mib[i].oid, objn, oid, objn) == 0)
135static SnmpMibEntry *mib_alloc(SnmpAgentCtx &c,
const uint32_t *oid,
size_t n)
139 SnmpMibEntry *e = &c.mib[c.mib_count++];
140 memset(e, 0,
sizeof(*e));
141 for (
size_t i = 0; i < n; i++)
147void snmp_agent_init(
const char *ro_community)
149 s_agent.mib_count = 0;
150 s_agent.rw_set =
false;
151 s_agent.rw[0] =
'\0';
153 strncpy(s_agent.ro, ro,
sizeof(s_agent.ro) - 1);
154 s_agent.ro[
sizeof(s_agent.ro) - 1] =
'\0';
157void snmp_agent_set_rw_community(
const char *rw_community)
159 if (!rw_community || !rw_community[0])
161 s_agent.rw_set =
false;
162 s_agent.rw[0] =
'\0';
165 strncpy(s_agent.rw, rw_community,
sizeof(s_agent.rw) - 1);
166 s_agent.rw[
sizeof(s_agent.rw) - 1] =
'\0';
167 s_agent.rw_set =
true;
170bool snmp_agent_add_string(
const uint32_t *oid,
size_t oid_len,
const char *value, SnmpSetFn setter)
172 SnmpMibEntry *e = mib_alloc(s_agent, oid, oid_len);
175 e->val.type = (uint8_t)SnmpTag::BER_OCTET_STRING;
182bool snmp_agent_add_integer(
const uint32_t *oid,
size_t oid_len,
long value, SnmpSetFn setter)
184 SnmpMibEntry *e = mib_alloc(s_agent, oid, oid_len);
187 e->val.type = (uint8_t)SnmpTag::BER_INTEGER;
193bool snmp_agent_add_dynamic(
const uint32_t *oid,
size_t oid_len, uint8_t type, SnmpGetFn getter)
195 SnmpMibEntry *e = mib_alloc(s_agent, oid, oid_len);
203static bool sys_uptime_get(SnmpValue *out)
205 out->type = (uint8_t)SnmpTag::SNMP_TIMETICKS;
206 out->uval = snmp_uptime_cs();
210void snmp_agent_set_system(
const char *descr,
const char *contact,
const char *name,
const char *location,
213 const uint32_t o_descr[] = {1, 3, 6, 1, 2, 1, 1, 1, 0};
214 const uint32_t o_oid[] = {1, 3, 6, 1, 2, 1, 1, 2, 0};
215 const uint32_t o_uptime[] = {1, 3, 6, 1, 2, 1, 1, 3, 0};
216 const uint32_t o_contact[] = {1, 3, 6, 1, 2, 1, 1, 4, 0};
217 const uint32_t o_name[] = {1, 3, 6, 1, 2, 1, 1, 5, 0};
218 const uint32_t o_loc[] = {1, 3, 6, 1, 2, 1, 1, 6, 0};
219 const uint32_t o_svc[] = {1, 3, 6, 1, 2, 1, 1, 7, 0};
221 snmp_agent_add_string(o_descr, 9, descr);
223 SnmpMibEntry *e = mib_alloc(s_agent, o_oid, 9);
226 e->val.type = (uint8_t)SnmpTag::BER_OID;
227 e->val.oid = g_sys_object_id;
228 e->val.oid_len =
sizeof(g_sys_object_id) /
sizeof(g_sys_object_id[0]);
231 snmp_agent_add_dynamic(o_uptime, 9, (uint8_t)SnmpTag::SNMP_TIMETICKS, sys_uptime_get);
232 snmp_agent_add_string(o_contact, 9, contact);
233 snmp_agent_add_string(o_name, 9, name);
234 snmp_agent_add_string(o_loc, 9, location);
235 snmp_agent_add_integer(o_svc, 9, services);
242static void enc_value(BerEnc *e,
const SnmpValue *v)
246 case (uint8_t)SnmpTag::BER_INTEGER:
247 ber_put_integer(e, v->ival);
249 case (uint8_t)SnmpTag::BER_OCTET_STRING:
250 case (uint8_t)SnmpTag::SNMP_OPAQUE:
251 ber_put_octet_string(e, v->type, (
const uint8_t *)v->str, v->str_len);
253 case (uint8_t)SnmpTag::BER_OID:
254 ber_put_oid(e, v->oid, v->oid_len);
256 case (uint8_t)SnmpTag::SNMP_TIMETICKS:
257 case (uint8_t)SnmpTag::SNMP_COUNTER32:
258 case (uint8_t)SnmpTag::SNMP_GAUGE32:
259 ber_put_uint(e, v->type, v->uval);
261 case (uint8_t)SnmpTag::SNMP_IPADDRESS: {
262 uint8_t ip[4] = {(uint8_t)(v->uval >> 24), (uint8_t)(v->uval >> 16), (uint8_t)(v->uval >> 8),
264 ber_put_octet_string(e, (uint8_t)SnmpTag::SNMP_IPADDRESS, ip, 4);
267 case (uint8_t)SnmpTag::BER_NULL:
273 ber_put_tlv(e, v->type,
nullptr, 0);
280static bool dec_value(BerDec *d, SnmpValue *v, uint32_t *oidbuf)
282 memset(v, 0,
sizeof(*v));
283 size_t save = d->pos;
286 if (!ber_read_header(d, &tag, &len))
291 case (uint8_t)SnmpTag::BER_INTEGER:
293 return ber_read_integer(d, &v->ival);
294 case (uint8_t)SnmpTag::SNMP_TIMETICKS:
295 case (uint8_t)SnmpTag::SNMP_COUNTER32:
296 case (uint8_t)SnmpTag::SNMP_GAUGE32:
297 case (uint8_t)SnmpTag::SNMP_IPADDRESS: {
299 for (
size_t i = 0; i < len; i++)
300 acc = (acc << 8) | d->buf[d->pos + i];
305 case (uint8_t)SnmpTag::BER_OCTET_STRING:
306 case (uint8_t)SnmpTag::SNMP_OPAQUE:
307 v->str = (
const char *)(d->buf + d->pos);
311 case (uint8_t)SnmpTag::BER_OID:
350static SnmpReqCtx s_req;
356static void snmp_apply_set_all(
size_t nvb,
bool v2c,
long *err_status,
long *err_index)
358 for (
size_t i = 0; i < nvb; i++)
360 const SnmpMibEntry *en = mib_find_exact(s_agent, s_req.in[i].oid, s_req.in[i].oid_len);
363 e = (int)SnmpErr::SNMP_ERR_NO_SUCH_NAME;
364 else if (!en->setter)
365 e = (!v2c) ? (
int)SnmpErr::SNMP_ERR_READ_ONLY : (int)SnmpErr::SNMP_ERR_NOT_WRITABLE;
366 else if (!en->setter(&s_req.in[i].val))
367 e = (!v2c) ? (int)SnmpErr::SNMP_ERR_BAD_VALUE : (int)SnmpErr::SNMP_ERR_WRONG_TYPE;
371 *err_index = (long)(i + 1);
376static bool community_eq(
const char *stored,
const char *p,
size_t len)
378 return stored[0] !=
'\0' && strnlen(stored, len + 1) == len && memcmp(stored, p, len) == 0;
381static size_t encode_pdu(
long request_id,
long err_status,
long err_index,
const OutVb *out,
size_t nout, uint8_t *buf,
385 ber_enc_init(&e, buf, cap);
386 size_t pdu = ber_seq_begin(&e, (uint8_t)SnmpTag::SNMP_PDU_RESPONSE);
387 ber_put_integer(&e, request_id);
388 ber_put_integer(&e, err_status);
389 ber_put_integer(&e, err_index);
390 size_t vbl = ber_seq_begin(&e, (uint8_t)SnmpTag::BER_SEQUENCE);
391 for (
size_t i = 0; i < nout; i++)
393 size_t vb = ber_seq_begin(&e, (uint8_t)SnmpTag::BER_SEQUENCE);
394 ber_put_oid(&e, out[i].oid, out[i].oid_len);
395 enc_value(&e, &out[i].val);
398 ber_seq_end(&e, vbl);
399 ber_seq_end(&e, pdu);
400 return e.ok ? e.len : 0;
407size_t snmp_dispatch_pdu(
const uint8_t *pdu,
size_t pdu_len,
bool allow_write,
bool v2c, uint8_t *out,
size_t out_cap)
410 ber_dec_init(&d, pdu, pdu_len);
414 if (!ber_read_header(&d, &pdu_tag, &pdu_clen))
420 if (!ber_read_integer(&d, &request_id) || !ber_read_integer(&d, &field2) || !ber_read_integer(&d, &field3))
425 if (!ber_read_header(&d, &vbl_tag, &vbl_len) || vbl_tag != (uint8_t)SnmpTag::BER_SEQUENCE)
427 size_t vbl_end = d.pos + vbl_len;
431 while (d.pos < vbl_end && d.ok)
437 if (!ber_read_header(&d, &vt, &vlen) || vt != (uint8_t)SnmpTag::BER_SEQUENCE)
439 if (!ber_read_oid(&d, s_req.in[nvb].oid,
SNMP_MAX_OID_LEN, &s_req.in[nvb].oid_len))
441 if (!dec_value(&d, &s_req.in[nvb].val, s_req.in[nvb].valoid))
448 long err_status = (int)SnmpErr::SNMP_ERR_NO_ERROR;
452 if (pdu_tag == (uint8_t)SnmpTag::SNMP_PDU_GET || pdu_tag == (uint8_t)SnmpTag::SNMP_PDU_GETNEXT)
454 for (
size_t i = 0; i < nvb; i++)
456 const SnmpMibEntry *en = (pdu_tag == (uint8_t)SnmpTag::SNMP_PDU_GET)
457 ? mib_find_exact(s_agent, s_req.in[i].oid, s_req.in[i].oid_len)
458 : mib_find_next(s_agent, s_req.in[i].oid, s_req.in[i].oid_len);
460 bool ok = en && fetch_value(en, &val);
466 err_status = (int)SnmpErr::SNMP_ERR_NO_SUCH_NAME;
467 err_index = (long)(i + 1);
469 for (
size_t k = 0; k < nvb; k++)
471 s_req.out[k].oid = s_req.in[k].oid;
472 s_req.out[k].oid_len = s_req.in[k].oid_len;
473 s_req.out[k].val = s_req.in[k].val;
481 if (pdu_tag == (uint8_t)SnmpTag::SNMP_PDU_GET)
483 bool inst = en || mib_object_exists(s_agent, s_req.in[i].oid, s_req.in[i].oid_len);
484 val.type = inst ? (uint8_t)SnmpTag::SNMP_NO_SUCH_INSTANCE : (uint8_t)SnmpTag::SNMP_NO_SUCH_OBJECT;
487 val.type = (uint8_t)SnmpTag::SNMP_END_OF_MIB_VIEW;
488 s_req.out[nout].oid = en ? en->oid : s_req.in[i].oid;
489 s_req.out[nout].oid_len = en ? en->oid_len : s_req.in[i].oid_len;
490 s_req.out[nout].val = val;
494 s_req.out[nout].oid = (pdu_tag == (uint8_t)SnmpTag::SNMP_PDU_GETNEXT) ? en->oid : s_req.in[i].oid;
495 s_req.out[nout].oid_len =
496 (pdu_tag == (uint8_t)SnmpTag::SNMP_PDU_GETNEXT) ? en->oid_len : s_req.in[i].oid_len;
497 s_req.out[nout].val = val;
501 else if (pdu_tag == (uint8_t)SnmpTag::SNMP_PDU_GETBULK)
505 long non_rep = field2 < 0 ? 0 : field2;
506 long max_rep = field3 < 0 ? 0 : field3;
507 if ((
size_t)non_rep > nvb)
513 const SnmpMibEntry *en = mib_find_next(s_agent, s_req.in[i].oid, s_req.in[i].oid_len);
516 s_req.out[nout].oid = en->oid;
517 s_req.out[nout].oid_len = en->oid_len;
518 fetch_value(en, &s_req.out[nout].val);
522 s_req.out[nout].oid = s_req.in[i].oid;
523 s_req.out[nout].oid_len = s_req.in[i].oid_len;
524 s_req.out[nout].val.type = (uint8_t)SnmpTag::SNMP_END_OF_MIB_VIEW;
530 size_t nrep = nvb - (size_t)non_rep;
534 for (
size_t r = 0; r < nrep; r++)
536 cur_oid[r] = s_req.in[non_rep + r].oid;
537 cur_len[r] = s_req.in[non_rep + r].oid_len;
546 s_req.out[nout].oid = cur_oid[r];
547 s_req.out[nout].oid_len = cur_len[r];
548 s_req.out[nout].val.type = (uint8_t)SnmpTag::SNMP_END_OF_MIB_VIEW;
552 const SnmpMibEntry *en = mib_find_next(s_agent, cur_oid[r], cur_len[r]);
555 s_req.out[nout].oid = en->oid;
556 s_req.out[nout].oid_len = en->oid_len;
557 fetch_value(en, &s_req.out[nout].val);
558 cur_oid[r] = en->oid;
559 cur_len[r] = en->oid_len;
563 s_req.out[nout].oid = cur_oid[r];
564 s_req.out[nout].oid_len = cur_len[r];
565 s_req.out[nout].val.type = (uint8_t)SnmpTag::SNMP_END_OF_MIB_VIEW;
572 else if (pdu_tag == (uint8_t)SnmpTag::SNMP_PDU_SET)
576 for (
size_t k = 0; k < nvb; k++)
578 s_req.out[k].oid = s_req.in[k].oid;
579 s_req.out[k].oid_len = s_req.in[k].oid_len;
580 s_req.out[k].val = s_req.in[k].val;
584 err_status = (!v2c) ? (
int)SnmpErr::SNMP_ERR_NO_SUCH_NAME : (int)SnmpErr::SNMP_ERR_NO_ACCESS;
589 snmp_apply_set_all(nvb, v2c, &err_status, &err_index);
597 size_t n = encode_pdu(request_id, err_status, err_index, s_req.out, nout, out, out_cap);
598 if (n == 0 && nout > 0)
601 n = encode_pdu(request_id, (
int)SnmpErr::SNMP_ERR_TOO_BIG, 0, s_req.out, 0, out, out_cap);
610size_t snmp_agent_process(
const uint8_t *req,
size_t req_len, uint8_t *resp,
size_t resp_cap)
613 ber_dec_init(&d, req, req_len);
617 if (!ber_read_header(&d, &tag, &len) || tag != (uint8_t)SnmpTag::BER_SEQUENCE)
621 if (!ber_read_integer(&d, &version))
624 if (version == (
int)SnmpVersion::SNMP_V3)
626#if DETWS_ENABLE_SNMP_V3
627 return snmp_v3_process(req, req_len, resp, resp_cap);
632 if (version != (
int)SnmpVersion::SNMP_V1 && version != (
int)SnmpVersion::SNMP_V2C)
637 if (!ber_read_header(&d, &ctag, &clen) || ctag != (uint8_t)SnmpTag::BER_OCTET_STRING)
639 const char *community = (
const char *)(d.buf + d.pos);
640 size_t community_len = clen;
643 bool is_rw = s_agent.rw_set && community_eq(s_agent.rw, community, community_len);
644 bool is_ro = is_rw || community_eq(s_agent.ro, community, community_len);
650 size_t pn = snmp_dispatch_pdu(req + d.pos, req_len - d.pos, is_rw, version == (
int)SnmpVersion::SNMP_V2C, pdubuf,
656 ber_enc_init(&e, resp, resp_cap);
657 size_t msg = ber_seq_begin(&e, (uint8_t)SnmpTag::BER_SEQUENCE);
658 ber_put_integer(&e, version);
659 ber_put_octet_string(&e, (uint8_t)SnmpTag::BER_OCTET_STRING, (
const uint8_t *)community, community_len);
660 ber_put_raw(&e, pdubuf, pn);
661 ber_seq_end(&e, msg);
662 return e.ok ? e.len : 0;
679SnmpUdpCtx s_snmp_udp;
682static void snmp_udp_handler(
const uint8_t *data,
size_t len,
struct DetUdpPeer *peer,
void *ctx)
685 size_t rn = snmp_agent_process(data, len, s_snmp_udp.tx,
sizeof(s_snmp_udp.tx));
690void snmp_agent_begin_udp(uint16_t port)
#define SNMP_MAX_OID_LEN
Maximum sub-identifiers (arcs) in an SNMP object identifier.
#define DETWS_SNMP_DEFAULT_RO_COMMUNITY
Default read-only community (overridable at runtime via snmp_agent_init). Deployments SHOULD change t...
#define SNMP_MSG_BUF_SIZE
Static request/response datagram buffers for the SNMP UDP agent.
#define SNMP_COMMUNITY_MAX
Maximum SNMP community-string length (including null terminator).
#define SNMP_MAX_MIB_ENTRIES
Maximum registered MIB objects (the agent's fixed OID table).
#define SNMP_MAX_VARBINDS
Maximum variable bindings the agent will emit in one response.
Zero-heap SNMP v1/v2c agent: PDU processing + a fixed MIB table.
SNMPv3 User-based Security Model (USM) layer (DETWS_ENABLE_SNMP_V3).
bool det_udp_send(struct DetUdpPeer *peer, const uint8_t *data, size_t len)
Send a datagram back to the peer captured during the handler call.
bool det_udp_listen(uint16_t port, DetUdpHandler handler, void *ctx)
Bind a UDP port and route incoming datagrams to handler.
Layer 4 (Transport) - connectionless UDP datagram service.