20#if PC_ENABLE_SNMP_TRAP
26static uint32_t pc_snmp_v3_uptime_s()
28 return (uint32_t)(millis() / 1000ULL);
31static uint32_t pc_snmp_v3_uptime_s()
38static const uint32_t kUsmStatsBase[] = {1, 3, 6, 1, 6, 3, 15, 1, 1};
39enum class UsmStat : uint8_t
41 USM_STAT_NOT_IN_TIME = 2,
42 USM_STAT_UNKNOWN_USER = 3,
43 USM_STAT_UNKNOWN_ENGINE = 4,
44 USM_STAT_WRONG_DIGEST = 5,
58 uint8_t engine_id[
SNMP_V3_ENGINEID_MAX] = {0x80, 0x00, 0xC0, 0xDE, 0x05, 0x01, 0x02, 0x03, 0x04};
59 size_t engine_id_len = 9;
63 uint8_t auth_key[SNMP_USM_KEY_LEN];
64 uint8_t priv_key[SNMP_USM_KEY_LEN];
65 bool auth_set =
false;
66 bool priv_set =
false;
67 uint32_t salt_ctr = 0;
70 uint32_t stat_unknown_engine = 0;
71 uint32_t stat_unknown_user = 0;
72 uint32_t stat_wrong_digest = 0;
73 uint32_t stat_not_in_time = 0;
74 uint32_t stat_decrypt = 0;
93 SNMP_V3_PRIV_PARAM_LEN <=
94 sizeof(SnmpV3Ctx::v3_sec),
95 "v3_sec is too small for SNMP_V3_ENGINEID_MAX + SNMP_V3_USER_MAX: raise v3_sec or lower the maxima");
101void pc_snmp_v3_init(
const uint8_t *engine_id,
size_t engine_id_len)
105 memcpy(s_v3.engine_id, engine_id, engine_id_len);
106 s_v3.engine_id_len = engine_id_len;
109 s_v3.auth_set =
false;
110 s_v3.priv_set =
false;
113void pc_snmp_v3_set_user(
const char *user,
const char *auth_pass,
const char *priv_pass)
115 strncpy(s_v3.user, user ? user :
"", sizeof(s_v3.user) - 1);
116 s_v3.user[
sizeof(s_v3.user) - 1] =
'\0';
117 s_v3.auth_set = auth_pass && auth_pass[0];
118 s_v3.priv_set = priv_pass && priv_pass[0];
121 pc_snmp_usm_localize_key(auth_pass, s_v3.engine_id, s_v3.engine_id_len, s_v3.auth_key);
125 pc_snmp_usm_localize_key(priv_pass, s_v3.engine_id, s_v3.engine_id_len, s_v3.priv_key);
129void pc_snmp_v3_set_boots(uint32_t boots)
133uint32_t pc_snmp_v3_get_boots()
142static bool ct_eq(
const uint8_t *a,
const uint8_t *b,
size_t n)
145 for (
size_t i = 0; i < n; i++)
147 r |= (uint8_t)(a[i] ^ b[i]);
153static bool parse_scoped(
const uint8_t *buf,
size_t len,
const uint8_t **ctxname,
size_t *ctxname_len,
154 const uint8_t **pdu,
size_t *pdu_len)
157 pc_ber_dec_init(&d, buf, len);
160 if (!pc_ber_read_header(&d, &tag, &l) || tag != (uint8_t)SnmpTag::BER_SEQUENCE)
164 if (!pc_ber_read_header(&d, &tag, &l) || tag != (uint8_t)SnmpTag::BER_OCTET_STRING)
169 if (!pc_ber_read_header(&d, &tag, &l) || tag != (uint8_t)SnmpTag::BER_OCTET_STRING)
173 *ctxname = d.buf + d.pos;
176 size_t pdu_start = d.pos;
177 if (!pc_ber_read_header(&d, &tag, &l))
181 *pdu = buf + pdu_start;
182 *pdu_len = (size_t)(d.pos - pdu_start) + l;
187static long inner_request_id(
const uint8_t *mdata,
size_t mdata_len,
bool priv)
193 const uint8_t *ctxname;
197 if (!parse_scoped(mdata, mdata_len, &ctxname, &ctxname_len, &pdu, &pdu_len))
202 pc_ber_dec_init(&d, pdu, pdu_len);
209 if (!pc_ber_read_header(&d, &tag, &l) || !pc_ber_read_integer(&d, &rid))
217static size_t build_message(
long msg_id,
bool auth,
bool priv,
const uint8_t *scoped,
size_t scoped_len, uint8_t *resp,
220 uint32_t now = pc_snmp_v3_uptime_s();
221 const uint8_t *data_ptr = scoped;
222 size_t data_len = scoped_len;
223 uint8_t salt[SNMP_V3_PRIV_PARAM_LEN];
232 memcpy(iv + 8, salt, SNMP_V3_PRIV_PARAM_LEN);
233 if (scoped_len >
sizeof(s_v3.v3_d))
237 pc_snmp_aes128_cfb(s_v3.priv_key, iv, scoped, s_v3.v3_d, scoped_len,
true);
238 data_ptr = s_v3.v3_d;
243 pc_ber_enc_init(&se, s_v3.v3_sec,
sizeof(s_v3.v3_sec));
244 size_t ss = pc_ber_seq_begin(&se, (uint8_t)SnmpTag::BER_SEQUENCE);
245 pc_ber_put_octet_string(&se, (uint8_t)SnmpTag::BER_OCTET_STRING, s_v3.engine_id, s_v3.engine_id_len);
246 pc_ber_put_integer(&se, (
long)s_v3.boots);
247 pc_ber_put_integer(&se, (
long)now);
248 pc_ber_put_octet_string(&se, (uint8_t)SnmpTag::BER_OCTET_STRING, (
const uint8_t *)s_v3.user,
253 auth_off = se.len + 2;
254 uint8_t zeros[SNMP_V3_AUTH_PARAM_LEN];
255 memset(zeros, 0,
sizeof(zeros));
256 pc_ber_put_octet_string(&se, (uint8_t)SnmpTag::BER_OCTET_STRING, zeros, SNMP_V3_AUTH_PARAM_LEN);
260 pc_ber_put_octet_string(&se, (uint8_t)SnmpTag::BER_OCTET_STRING,
nullptr, 0);
264 pc_ber_put_octet_string(&se, (uint8_t)SnmpTag::BER_OCTET_STRING, salt, SNMP_V3_PRIV_PARAM_LEN);
268 pc_ber_put_octet_string(&se, (uint8_t)SnmpTag::BER_OCTET_STRING,
nullptr, 0);
270 pc_ber_seq_end(&se, ss);
276 size_t sec_len = se.len;
280 pc_ber_enc_init(&e, resp, pc_resp_cap);
281 size_t msg = pc_ber_seq_begin(&e, (uint8_t)SnmpTag::BER_SEQUENCE);
282 pc_ber_put_integer(&e, (
int)SnmpVersion::SNMP_V3);
283 size_t hdr = pc_ber_seq_begin(&e, (uint8_t)SnmpTag::BER_SEQUENCE);
284 pc_ber_put_integer(&e, msg_id);
285 pc_ber_put_integer(&e, 65507);
286 uint8_t fl = (uint8_t)((auth ? 0x01 : 0) | (priv ? 0x02 : 0));
287 pc_ber_put_octet_string(&e, (uint8_t)SnmpTag::BER_OCTET_STRING, &fl, 1);
288 pc_ber_put_integer(&e, 3);
289 pc_ber_seq_end(&e, hdr);
290 pc_ber_put_octet_string(&e, (uint8_t)SnmpTag::BER_OCTET_STRING, s_v3.v3_sec, sec_len);
291 size_t sec_value_pos = e.len - sec_len;
294 pc_ber_put_octet_string(&e, (uint8_t)SnmpTag::BER_OCTET_STRING, data_ptr, data_len);
298 pc_ber_put_raw(&e, data_ptr, data_len);
300 pc_ber_seq_end(&e, msg);
305 size_t total = e.len;
310 pc_hmac_sha256(s_v3.auth_key, SNMP_USM_KEY_LEN, resp, total, mac);
311 memcpy(resp + sec_value_pos + auth_off, mac, SNMP_V3_AUTH_PARAM_LEN);
317static size_t build_report(
long msg_id,
bool auth, uint32_t stat, uint32_t count,
long request_id, uint8_t *resp,
321 for (
int i = 0; i < 9; i++)
323 oid[i] = kUsmStatsBase[i];
329 pc_ber_enc_init(&e, s_v3.v3_b,
sizeof(s_v3.v3_b));
330 size_t pdu = pc_ber_seq_begin(&e, (uint8_t)SnmpTag::SNMP_PDU_REPORT);
331 pc_ber_put_integer(&e, request_id);
332 pc_ber_put_integer(&e, 0);
333 pc_ber_put_integer(&e, 0);
334 size_t vbl = pc_ber_seq_begin(&e, (uint8_t)SnmpTag::BER_SEQUENCE);
335 size_t vb = pc_ber_seq_begin(&e, (uint8_t)SnmpTag::BER_SEQUENCE);
336 pc_ber_put_oid(&e, oid, 11);
337 pc_ber_put_uint(&e, (uint8_t)SnmpTag::SNMP_COUNTER32, count);
338 pc_ber_seq_end(&e, vb);
339 pc_ber_seq_end(&e, vbl);
340 pc_ber_seq_end(&e, pdu);
347 pc_ber_enc_init(&sc, s_v3.v3_c,
sizeof(s_v3.v3_c));
348 size_t s = pc_ber_seq_begin(&sc, (uint8_t)SnmpTag::BER_SEQUENCE);
349 pc_ber_put_octet_string(&sc, (uint8_t)SnmpTag::BER_OCTET_STRING, s_v3.engine_id, s_v3.engine_id_len);
350 pc_ber_put_octet_string(&sc, (uint8_t)SnmpTag::BER_OCTET_STRING,
nullptr, 0);
351 pc_ber_put_raw(&sc, s_v3.v3_b, e.len);
352 pc_ber_seq_end(&sc, s);
358 return build_message(msg_id, auth,
false, s_v3.v3_c, sc.len, resp, pc_resp_cap);
365size_t pc_snmp_v3_process(
const uint8_t *req,
size_t req_len, uint8_t *resp,
size_t pc_resp_cap)
368 pc_ber_dec_init(&d, req, req_len);
371 if (!pc_ber_read_header(&d, &tag, &l) || tag != (uint8_t)SnmpTag::BER_SEQUENCE)
376 if (!pc_ber_read_integer(&d, &version) || version != (
int)SnmpVersion::SNMP_V3)
382 if (!pc_ber_read_header(&d, &tag, &l) || tag != (uint8_t)SnmpTag::BER_SEQUENCE)
389 if (!pc_ber_read_integer(&d, &msg_id) || !pc_ber_read_integer(&d, &msg_max))
395 if (!pc_ber_read_header(&d, &tag, &flags_len) || tag != (uint8_t)SnmpTag::BER_OCTET_STRING || flags_len < 1)
399 uint8_t flags = d.buf[d.pos];
401 if (!pc_ber_read_integer(&d, &sec_model) || sec_model != 3)
405 bool req_auth = flags & 0x01;
406 bool req_priv = flags & 0x02;
407 if (req_priv && !req_auth)
414 if (!pc_ber_read_header(&d, &tag, &sec_len) || tag != (uint8_t)SnmpTag::BER_OCTET_STRING)
418 const uint8_t *sec = d.buf + d.pos;
422 pc_ber_dec_init(&sd, sec, sec_len);
423 if (!pc_ber_read_header(&sd, &tag, &l) || tag != (uint8_t)SnmpTag::BER_SEQUENCE)
428 if (!pc_ber_read_header(&sd, &tag, &eid_len) || tag != (uint8_t)SnmpTag::BER_OCTET_STRING)
432 const uint8_t *eid = sd.buf + sd.pos;
436 if (!pc_ber_read_integer(&sd, &req_boots) || !pc_ber_read_integer(&sd, &req_time))
441 if (!pc_ber_read_header(&sd, &tag, &uname_len) || tag != (uint8_t)SnmpTag::BER_OCTET_STRING)
445 const uint8_t *uname = sd.buf + sd.pos;
448 if (!pc_ber_read_header(&sd, &tag, &aparm_len) || tag != (uint8_t)SnmpTag::BER_OCTET_STRING)
452 const uint8_t *aparm = sd.buf + sd.pos;
453 size_t aparm_off = (size_t)(aparm - req);
456 if (!pc_ber_read_header(&sd, &tag, &pparm_len) || tag != (uint8_t)SnmpTag::BER_OCTET_STRING)
460 const uint8_t *pparm = sd.buf + sd.pos;
468 const uint8_t *mdata = d.buf + d.pos;
469 size_t mdata_len = req_len - d.pos;
472 bool engine_match = (eid_len == s_v3.engine_id_len) && (memcmp(eid, s_v3.engine_id, eid_len) == 0);
475 s_v3.stat_unknown_engine++;
476 return build_report(msg_id,
false, (
int)UsmStat::USM_STAT_UNKNOWN_ENGINE, s_v3.stat_unknown_engine,
477 inner_request_id(mdata, mdata_len, req_priv), resp, pc_resp_cap);
486 if (!s_v3.auth_set || !(uname_len == strnlen(s_v3.user,
SNMP_V3_USER_MAX) && s_v3.user[0] &&
487 memcmp(uname, s_v3.user, uname_len) == 0))
489 s_v3.stat_unknown_user++;
490 return build_report(msg_id,
false, (
int)UsmStat::USM_STAT_UNKNOWN_USER, s_v3.stat_unknown_user, 0, resp,
495 if (aparm_len != SNMP_V3_AUTH_PARAM_LEN || req_len >
sizeof(s_v3.v3_a))
497 s_v3.stat_wrong_digest++;
498 return build_report(msg_id,
false, (
int)UsmStat::USM_STAT_WRONG_DIGEST, s_v3.stat_wrong_digest, 0, resp,
501 memcpy(s_v3.v3_a, req, req_len);
502 memset(s_v3.v3_a + aparm_off, 0, SNMP_V3_AUTH_PARAM_LEN);
504 pc_hmac_sha256(s_v3.auth_key, SNMP_USM_KEY_LEN, s_v3.v3_a, req_len, mac);
505 if (!ct_eq(mac, aparm, SNMP_V3_AUTH_PARAM_LEN))
507 s_v3.stat_wrong_digest++;
508 return build_report(msg_id,
false, (
int)UsmStat::USM_STAT_WRONG_DIGEST, s_v3.stat_wrong_digest, 0, resp,
513 uint32_t now = pc_snmp_v3_uptime_s();
514 long dt = (long)now - req_time;
519 if ((uint32_t)req_boots != s_v3.boots || dt > 150)
521 s_v3.stat_not_in_time++;
522 return build_report(msg_id,
true, (
int)UsmStat::USM_STAT_NOT_IN_TIME, s_v3.stat_not_in_time, 0, resp,
527 const uint8_t *scoped;
531 if (!s_v3.priv_set || pparm_len != SNMP_V3_PRIV_PARAM_LEN)
534 return build_report(msg_id,
true, (
int)UsmStat::USM_STAT_DECRYPT, s_v3.stat_decrypt, 0, resp, pc_resp_cap);
537 pc_ber_dec_init(&md, mdata, mdata_len);
538 if (!pc_ber_read_header(&md, &tag, &l) || tag != (uint8_t)SnmpTag::BER_OCTET_STRING)
542 const uint8_t *ct = md.buf + md.pos;
544 if (ct_len >
sizeof(s_v3.v3_a))
552 memcpy(iv + 8, pparm, SNMP_V3_PRIV_PARAM_LEN);
553 pc_snmp_aes128_cfb(s_v3.priv_key, iv, ct, s_v3.v3_a, ct_len,
false);
560 scoped_len = mdata_len;
564 const uint8_t *ctxname;
568 if (!parse_scoped(scoped, scoped_len, &ctxname, &ctxname_len, &pdu, &pdu_len))
572 size_t rpdu = pc_snmp_dispatch_pdu(pdu, pdu_len,
true,
true, s_v3.v3_b,
sizeof(s_v3.v3_b));
580 pc_ber_enc_init(&sc, s_v3.v3_c,
sizeof(s_v3.v3_c));
581 size_t s = pc_ber_seq_begin(&sc, (uint8_t)SnmpTag::BER_SEQUENCE);
582 pc_ber_put_octet_string(&sc, (uint8_t)SnmpTag::BER_OCTET_STRING, s_v3.engine_id, s_v3.engine_id_len);
583 pc_ber_put_octet_string(&sc, (uint8_t)SnmpTag::BER_OCTET_STRING, ctxname, ctxname_len);
584 pc_ber_put_raw(&sc, s_v3.v3_b, rpdu);
585 pc_ber_seq_end(&sc, s);
591 return build_message(msg_id,
true, req_priv, s_v3.v3_c, sc.len, resp, pc_resp_cap);
594#if PC_ENABLE_SNMP_TRAP
601static bool send_v3_notify(
const char *dst_ip, uint16_t port, uint8_t pdu_tag, uint32_t request_id,
602 const uint32_t *trap_oid,
size_t trap_oid_len,
const SnmpVarbind *vbs,
size_t n)
611 pc_ber_enc_init(&e, s_v3.v3_b,
sizeof(s_v3.v3_b));
612 pc_snmp_notify_build_pdu(&e, pdu_tag, request_id, trap_oid, trap_oid_len, pc_snmp_v3_uptime_s() * 100, vbs, n);
620 pc_ber_enc_init(&sc, s_v3.v3_c,
sizeof(s_v3.v3_c));
621 size_t s = pc_ber_seq_begin(&sc, (uint8_t)SnmpTag::BER_SEQUENCE);
622 pc_ber_put_octet_string(&sc, (uint8_t)SnmpTag::BER_OCTET_STRING, s_v3.engine_id, s_v3.engine_id_len);
623 pc_ber_put_octet_string(&sc, (uint8_t)SnmpTag::BER_OCTET_STRING,
nullptr, 0);
624 pc_ber_put_raw(&sc, s_v3.v3_b, e.len);
625 pc_ber_seq_end(&sc, s);
632 size_t len = build_message((
long)request_id, s_v3.auth_set, s_v3.priv_set, s_v3.v3_c, sc.len, out,
sizeof(out));
636bool pc_snmp_trap_v3(
const char *dst_ip, uint16_t port,
const uint32_t *trap_oid,
size_t trap_oid_len,
637 const SnmpVarbind *vbs,
size_t n)
639 static uint32_t s_v3_trap_id = 1;
640 return send_v3_notify(dst_ip, port, (uint8_t)SnmpTag::SNMP_PDU_TRAPV2, s_v3_trap_id++, trap_oid, trap_oid_len, vbs,
648bool pc_snmp_inform_v3(
const char *dst_ip, uint16_t port, uint32_t request_id,
const uint32_t *trap_oid,
649 size_t trap_oid_len,
const SnmpVarbind *vbs,
size_t n)
651 return send_v3_notify(dst_ip, port, 0xA6 , request_id, trap_oid, trap_oid_len, vbs, n);
Fixed-width integer serializers into a raw uint8_t* buffer - one source of truth.
size_t pc_wr32be(uint8_t *p, uint32_t v)
Write v big-endian at p.
void pc_hmac_sha256(const uint8_t *key, size_t key_len, const uint8_t *data, size_t len, uint8_t mac[PC_HMAC_SHA256_LEN])
Compute HMAC-SHA2-256 over a single contiguous buffer.
HMAC-SHA2-256 (RFC 2104 + FIPS 198-1) - streaming context and one-shot API.
#define PC_HMAC_SHA256_LEN
HMAC-SHA2-256 output length in bytes.
#define SNMP_V3_USER_MAX
Maximum SNMPv3 USM user-name length (including null terminator).
#define SNMP_MSG_BUF_SIZE
Static request/response datagram buffers for the SNMP UDP agent.
#define SNMP_V3_ENGINEID_MAX
Maximum SNMPv3 authoritative engine-ID length in bytes (RFC 3411 allows 5..32).
bool pc_udp_sendto(const char *dst_ip, uint16_t dst_port, const uint8_t *data, size_t len)
Send a UDP datagram to an arbitrary destination (outbound client).
Layer 4 (Transport) - connectionless UDP datagram service.