22static uint32_t pc_snmp_uptime_cs()
24 return (uint32_t)(millis() / 10ULL);
27static uint32_t pc_snmp_uptime_cs()
58static SnmpAgentCtx s_agent;
65 "PC_SNMP_DEFAULT_RO_COMMUNITY must be a non-empty string literal");
68static const uint32_t g_sys_object_id[] = {1, 3, 6, 1, 4, 1, 49374};
74static int oid_cmp(
const uint32_t *a,
size_t an,
const uint32_t *b,
size_t bn)
76 size_t n = an < bn ? an : bn;
77 for (
size_t i = 0; i < n; i++)
99static const SnmpMibEntry *mib_find_exact(
const SnmpAgentCtx &c,
const uint32_t *oid,
size_t n)
101 for (
size_t i = 0; i < c.mib_count; i++)
103 if (oid_cmp(c.mib[i].oid, c.mib[i].oid_len, oid, n) == 0)
112static const SnmpMibEntry *mib_find_next(
const SnmpAgentCtx &c,
const uint32_t *oid,
size_t n)
114 const SnmpMibEntry *best =
nullptr;
115 for (
size_t i = 0; i < c.mib_count; i++)
117 if (oid_cmp(c.mib[i].oid, c.mib[i].oid_len, oid, n) > 0)
119 if (!best || oid_cmp(c.mib[i].oid, c.mib[i].oid_len, best->oid, best->oid_len) < 0)
128static bool fetch_value(
const SnmpMibEntry *en, SnmpValue *out)
132 return en->getter(out);
144static bool mib_object_exists(
const SnmpAgentCtx &c,
const uint32_t *oid,
size_t n)
146 for (
size_t i = 0; i < c.mib_count; i++)
148 size_t objn = c.mib[i].oid_len - 1;
149 if (objn <= n && oid_cmp(c.mib[i].oid, objn, oid, objn) == 0)
161static SnmpMibEntry *mib_alloc(SnmpAgentCtx &c,
const uint32_t *oid,
size_t n)
167 SnmpMibEntry *e = &c.mib[c.mib_count++];
168 memset(e, 0,
sizeof(*e));
169 for (
size_t i = 0; i < n; i++)
177void pc_snmp_agent_init(
const char *ro_community)
179 s_agent.mib_count = 0;
180 s_agent.rw_set =
false;
181 s_agent.rw[0] =
'\0';
183 strncpy(s_agent.ro, ro,
sizeof(s_agent.ro) - 1);
184 s_agent.ro[
sizeof(s_agent.ro) - 1] =
'\0';
187void pc_snmp_agent_set_rw_community(
const char *rw_community)
189 if (!rw_community || !rw_community[0])
191 s_agent.rw_set =
false;
192 s_agent.rw[0] =
'\0';
195 strncpy(s_agent.rw, rw_community,
sizeof(s_agent.rw) - 1);
196 s_agent.rw[
sizeof(s_agent.rw) - 1] =
'\0';
197 s_agent.rw_set =
true;
200bool pc_snmp_agent_add_string(
const uint32_t *oid,
size_t oid_len,
const char *value, SnmpSetFn setter)
202 SnmpMibEntry *e = mib_alloc(s_agent, oid, oid_len);
207 e->val.type = (uint8_t)SnmpTag::BER_OCTET_STRING;
214bool pc_snmp_agent_add_integer(
const uint32_t *oid,
size_t oid_len,
long value, SnmpSetFn setter)
216 SnmpMibEntry *e = mib_alloc(s_agent, oid, oid_len);
221 e->val.type = (uint8_t)SnmpTag::BER_INTEGER;
227bool pc_snmp_agent_add_dynamic(
const uint32_t *oid,
size_t oid_len, uint8_t type, SnmpGetFn getter)
229 SnmpMibEntry *e = mib_alloc(s_agent, oid, oid_len);
239static bool sys_uptime_get(SnmpValue *out)
241 out->type = (uint8_t)SnmpTag::SNMP_TIMETICKS;
242 out->uval = pc_snmp_uptime_cs();
246void pc_snmp_agent_set_system(
const char *descr,
const char *contact,
const char *name,
const char *location,
249 static const uint32_t o_descr[] = {1, 3, 6, 1, 2, 1, 1, 1, 0};
250 static const uint32_t o_oid[] = {1, 3, 6, 1, 2, 1, 1, 2, 0};
251 static const uint32_t o_uptime[] = {1, 3, 6, 1, 2, 1, 1, 3, 0};
252 static const uint32_t o_contact[] = {1, 3, 6, 1, 2, 1, 1, 4, 0};
253 static const uint32_t o_name[] = {1, 3, 6, 1, 2, 1, 1, 5, 0};
254 static const uint32_t o_loc[] = {1, 3, 6, 1, 2, 1, 1, 6, 0};
255 static const uint32_t o_svc[] = {1, 3, 6, 1, 2, 1, 1, 7, 0};
257 pc_snmp_agent_add_string(o_descr, 9, descr);
259 SnmpMibEntry *e = mib_alloc(s_agent, o_oid, 9);
262 e->val.type = (uint8_t)SnmpTag::BER_OID;
263 e->val.oid = g_sys_object_id;
264 e->val.oid_len =
sizeof(g_sys_object_id) /
sizeof(g_sys_object_id[0]);
267 pc_snmp_agent_add_dynamic(o_uptime, 9, (uint8_t)SnmpTag::SNMP_TIMETICKS, sys_uptime_get);
268 pc_snmp_agent_add_string(o_contact, 9, contact);
269 pc_snmp_agent_add_string(o_name, 9, name);
270 pc_snmp_agent_add_string(o_loc, 9, location);
271 pc_snmp_agent_add_integer(o_svc, 9, services);
278static void enc_value(BerEnc *e,
const SnmpValue *v)
282 case (uint8_t)SnmpTag::BER_INTEGER:
283 pc_ber_put_integer(e, v->ival);
285 case (uint8_t)SnmpTag::BER_OCTET_STRING:
286 case (uint8_t)SnmpTag::SNMP_OPAQUE:
287 pc_ber_put_octet_string(e, v->type, (
const uint8_t *)v->str, v->str_len);
289 case (uint8_t)SnmpTag::BER_OID:
290 pc_ber_put_oid(e, v->oid, v->oid_len);
292 case (uint8_t)SnmpTag::SNMP_TIMETICKS:
293 case (uint8_t)SnmpTag::SNMP_COUNTER32:
294 case (uint8_t)SnmpTag::SNMP_GAUGE32:
295 pc_ber_put_uint(e, v->type, v->uval);
297 case (uint8_t)SnmpTag::SNMP_IPADDRESS: {
298 uint8_t ip[4] = {(uint8_t)(v->uval >> 24), (uint8_t)(v->uval >> 16), (uint8_t)(v->uval >> 8),
300 pc_ber_put_octet_string(e, (uint8_t)SnmpTag::SNMP_IPADDRESS, ip, 4);
303 case (uint8_t)SnmpTag::BER_NULL:
309 pc_ber_put_tlv(e, v->type,
nullptr, 0);
316static bool dec_value(BerDec *d, SnmpValue *v, uint32_t *oidbuf)
318 memset(v, 0,
sizeof(*v));
319 size_t save = d->pos;
322 if (!pc_ber_read_header(d, &tag, &len))
329 case (uint8_t)SnmpTag::BER_INTEGER:
331 return pc_ber_read_integer(d, &v->ival);
332 case (uint8_t)SnmpTag::SNMP_TIMETICKS:
333 case (uint8_t)SnmpTag::SNMP_COUNTER32:
334 case (uint8_t)SnmpTag::SNMP_GAUGE32:
335 case (uint8_t)SnmpTag::SNMP_IPADDRESS: {
337 for (
size_t i = 0; i < len; i++)
339 acc = (acc << 8) | d->buf[d->pos + i];
345 case (uint8_t)SnmpTag::BER_OCTET_STRING:
346 case (uint8_t)SnmpTag::SNMP_OPAQUE:
347 v->str = (
const char *)(d->buf + d->pos);
351 case (uint8_t)SnmpTag::BER_OID:
392static SnmpReqCtx s_req;
398static void pc_snmp_apply_set_all(
size_t nvb,
bool v2c,
long *err_status,
long *err_index)
400 for (
size_t i = 0; i < nvb; i++)
402 const SnmpMibEntry *en = mib_find_exact(s_agent, s_req.in[i].oid, s_req.in[i].oid_len);
406 e = (int)SnmpErr::SNMP_ERR_NO_SUCH_NAME;
408 else if (!en->setter)
410 e = (!v2c) ? (
int)SnmpErr::SNMP_ERR_READ_ONLY : (int)SnmpErr::SNMP_ERR_NOT_WRITABLE;
412 else if (!en->setter(&s_req.in[i].val))
414 e = (!v2c) ? (
int)SnmpErr::SNMP_ERR_BAD_VALUE : (int)SnmpErr::SNMP_ERR_WRONG_TYPE;
421 *err_index = (long)(i + 1);
426static bool community_eq(
const char *stored,
const char *p,
size_t len)
431 return stored[0] !=
'\0' && strnlen(stored, len + 1) == len && memcmp(stored, p, len) == 0;
434static size_t encode_pdu(
long request_id,
long err_status,
long err_index,
const OutVb *out,
size_t nout, uint8_t *buf,
438 pc_ber_enc_init(&e, buf, cap);
439 size_t pdu = pc_ber_seq_begin(&e, (uint8_t)SnmpTag::SNMP_PDU_RESPONSE);
440 pc_ber_put_integer(&e, request_id);
441 pc_ber_put_integer(&e, err_status);
442 pc_ber_put_integer(&e, err_index);
443 size_t vbl = pc_ber_seq_begin(&e, (uint8_t)SnmpTag::BER_SEQUENCE);
444 for (
size_t i = 0; i < nout; i++)
446 size_t vb = pc_ber_seq_begin(&e, (uint8_t)SnmpTag::BER_SEQUENCE);
447 pc_ber_put_oid(&e, out[i].oid, out[i].oid_len);
448 enc_value(&e, &out[i].val);
449 pc_ber_seq_end(&e, vb);
451 pc_ber_seq_end(&e, vbl);
452 pc_ber_seq_end(&e, pdu);
453 return e.ok ? e.len : 0;
460size_t pc_snmp_dispatch_pdu(
const uint8_t *pdu,
size_t pdu_len,
bool allow_write,
bool v2c, uint8_t *out,
464 pc_ber_dec_init(&d, pdu, pdu_len);
468 if (!pc_ber_read_header(&d, &pdu_tag, &pdu_clen))
476 if (!pc_ber_read_integer(&d, &request_id) || !pc_ber_read_integer(&d, &field2) || !pc_ber_read_integer(&d, &field3))
483 if (!pc_ber_read_header(&d, &vbl_tag, &vbl_len) || vbl_tag != (uint8_t)SnmpTag::BER_SEQUENCE)
487 size_t vbl_end = d.pos + vbl_len;
493 while (d.pos < vbl_end && d.ok)
501 if (!pc_ber_read_header(&d, &vt, &vlen) || vt != (uint8_t)SnmpTag::BER_SEQUENCE)
505 if (!pc_ber_read_oid(&d, s_req.in[nvb].oid,
SNMP_MAX_OID_LEN, &s_req.in[nvb].oid_len))
509 if (!dec_value(&d, &s_req.in[nvb].val, s_req.in[nvb].valoid))
520 long err_status = (int)SnmpErr::SNMP_ERR_NO_ERROR;
524 if (pdu_tag == (uint8_t)SnmpTag::SNMP_PDU_GET || pdu_tag == (uint8_t)SnmpTag::SNMP_PDU_GETNEXT)
526 for (
size_t i = 0; i < nvb; i++)
528 const SnmpMibEntry *en = (pdu_tag == (uint8_t)SnmpTag::SNMP_PDU_GET)
529 ? mib_find_exact(s_agent, s_req.in[i].oid, s_req.in[i].oid_len)
530 : mib_find_next(s_agent, s_req.in[i].oid, s_req.in[i].oid_len);
532 bool ok = en && fetch_value(en, &val);
538 err_status = (int)SnmpErr::SNMP_ERR_NO_SUCH_NAME;
539 err_index = (long)(i + 1);
541 for (
size_t k = 0; k < nvb; k++)
543 s_req.out[k].oid = s_req.in[k].oid;
544 s_req.out[k].oid_len = s_req.in[k].oid_len;
545 s_req.out[k].val = s_req.in[k].val;
553 if (pdu_tag == (uint8_t)SnmpTag::SNMP_PDU_GET)
555 bool inst = en || mib_object_exists(s_agent, s_req.in[i].oid, s_req.in[i].oid_len);
556 val.type = inst ? (uint8_t)SnmpTag::SNMP_NO_SUCH_INSTANCE : (uint8_t)SnmpTag::SNMP_NO_SUCH_OBJECT;
560 val.type = (uint8_t)SnmpTag::SNMP_END_OF_MIB_VIEW;
562 s_req.out[nout].oid = en ? en->oid : s_req.in[i].oid;
563 s_req.out[nout].oid_len = en ? en->oid_len : s_req.in[i].oid_len;
564 s_req.out[nout].val = val;
568 s_req.out[nout].oid = (pdu_tag == (uint8_t)SnmpTag::SNMP_PDU_GETNEXT) ? en->oid : s_req.in[i].oid;
569 s_req.out[nout].oid_len =
570 (pdu_tag == (uint8_t)SnmpTag::SNMP_PDU_GETNEXT) ? en->oid_len : s_req.in[i].oid_len;
571 s_req.out[nout].val = val;
575 else if (pdu_tag == (uint8_t)SnmpTag::SNMP_PDU_GETBULK)
581 long non_rep = field2 < 0 ? 0 : field2;
582 long max_rep = field3 < 0 ? 0 : field3;
583 if ((
size_t)non_rep > nvb)
592 for (
long i = 0; i < non_rep; i++)
598 const SnmpMibEntry *en = mib_find_next(s_agent, s_req.in[i].oid, s_req.in[i].oid_len);
601 s_req.out[nout].oid = en->oid;
602 s_req.out[nout].oid_len = en->oid_len;
603 fetch_value(en, &s_req.out[nout].val);
607 s_req.out[nout].oid = s_req.in[i].oid;
608 s_req.out[nout].oid_len = s_req.in[i].oid_len;
609 s_req.out[nout].val.type = (uint8_t)SnmpTag::SNMP_END_OF_MIB_VIEW;
615 size_t nrep = nvb - (size_t)non_rep;
619 for (
size_t r = 0; r < nrep; r++)
621 cur_oid[r] = s_req.in[non_rep + r].oid;
622 cur_len[r] = s_req.in[non_rep + r].oid_len;
631 s_req.out[nout].oid = cur_oid[r];
632 s_req.out[nout].oid_len = cur_len[r];
633 s_req.out[nout].val.type = (uint8_t)SnmpTag::SNMP_END_OF_MIB_VIEW;
637 const SnmpMibEntry *en = mib_find_next(s_agent, cur_oid[r], cur_len[r]);
640 s_req.out[nout].oid = en->oid;
641 s_req.out[nout].oid_len = en->oid_len;
642 fetch_value(en, &s_req.out[nout].val);
643 cur_oid[r] = en->oid;
644 cur_len[r] = en->oid_len;
648 s_req.out[nout].oid = cur_oid[r];
649 s_req.out[nout].oid_len = cur_len[r];
650 s_req.out[nout].val.type = (uint8_t)SnmpTag::SNMP_END_OF_MIB_VIEW;
657 else if (pdu_tag == (uint8_t)SnmpTag::SNMP_PDU_SET)
661 for (
size_t k = 0; k < nvb; k++)
663 s_req.out[k].oid = s_req.in[k].oid;
664 s_req.out[k].oid_len = s_req.in[k].oid_len;
665 s_req.out[k].val = s_req.in[k].val;
669 err_status = (!v2c) ? (
int)SnmpErr::SNMP_ERR_NO_SUCH_NAME : (int)SnmpErr::SNMP_ERR_NO_ACCESS;
674 pc_snmp_apply_set_all(nvb, v2c, &err_status, &err_index);
682 size_t n = encode_pdu(request_id, err_status, err_index, s_req.out, nout, out, out_cap);
683 if (n == 0 && nout > 0)
686 n = encode_pdu(request_id, (
int)SnmpErr::SNMP_ERR_TOO_BIG, 0, s_req.out, 0, out, out_cap);
695size_t pc_snmp_agent_process(
const uint8_t *req,
size_t req_len, uint8_t *resp,
size_t pc_resp_cap)
698 pc_ber_dec_init(&d, req, req_len);
702 if (!pc_ber_read_header(&d, &tag, &len) || tag != (uint8_t)SnmpTag::BER_SEQUENCE)
708 if (!pc_ber_read_integer(&d, &version))
713 if (version == (
int)SnmpVersion::SNMP_V3)
716 return pc_snmp_v3_process(req, req_len, resp, pc_resp_cap);
721 if (version != (
int)SnmpVersion::SNMP_V1 && version != (
int)SnmpVersion::SNMP_V2C)
728 if (!pc_ber_read_header(&d, &ctag, &clen) || ctag != (uint8_t)SnmpTag::BER_OCTET_STRING)
732 const char *community = (
const char *)(d.buf + d.pos);
733 size_t community_len = clen;
736 bool is_rw = s_agent.rw_set && community_eq(s_agent.rw, community, community_len);
737 bool is_ro = is_rw || community_eq(s_agent.ro, community, community_len);
745 size_t pn = pc_snmp_dispatch_pdu(req + d.pos, req_len - d.pos, is_rw, version == (
int)SnmpVersion::SNMP_V2C, pdubuf,
753 pc_ber_enc_init(&e, resp, pc_resp_cap);
754 size_t msg = pc_ber_seq_begin(&e, (uint8_t)SnmpTag::BER_SEQUENCE);
755 pc_ber_put_integer(&e, version);
756 pc_ber_put_octet_string(&e, (uint8_t)SnmpTag::BER_OCTET_STRING, (
const uint8_t *)community, community_len);
757 pc_ber_put_raw(&e, pdubuf, pn);
758 pc_ber_seq_end(&e, msg);
759 return e.ok ? e.len : 0;
774SnmpUdpCtx s_snmp_udp;
777static void pc_snmp_udp_handler(
const uint8_t *data,
size_t len,
const struct pc_udp_peer *peer,
void *ctx)
780 size_t rn = pc_snmp_agent_process(data, len, s_snmp_udp.tx,
sizeof(s_snmp_udp.tx));
787void pc_snmp_agent_begin_udp(uint16_t port)
#define SNMP_MAX_OID_LEN
Maximum sub-identifiers (arcs) in an SNMP object identifier.
#define PC_SNMP_DEFAULT_RO_COMMUNITY
Default read-only community (overridable at runtime via pc_snmp_agent_init). Deployments SHOULD chang...
#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.
bool pc_udp_listen(uint16_t port, pc_udp_handler handler, void *ctx)
Bind a UDP port and route incoming datagrams to handler.
bool pc_udp_send(const struct pc_udp_peer *peer, const uint8_t *data, size_t len)
Send a datagram back to the peer captured during the handler call.
Layer 4 (Transport) - connectionless UDP datagram service.