lwIP SNMP v1/v2c/v3 Manager support

Created, tested and used by MEP (www.mep-info.com).

---
 src/Filelists.mk                           |   1 +
 src/apps/snmp/snmp_manager.c (new)         | 640 +++++++++++++++++++++++++++++
 src/apps/snmp/snmp_msg.c                   | 371 ++++++++++-------
 src/apps/snmp/snmp_msg.h                   |  10 +
 src/apps/snmp/snmp_traps.c                 |  10 +-
 src/include/lwip/apps/snmp_manager.h (new) |  61 +++
 src/include/lwip/apps/snmp_opts.h          |   7 +
 src/include/lwip/apps/snmpv3.h             |   7 +
 8 files changed, 961 insertions(+), 146 deletions(-)

diff --git a/src/Filelists.mk b/src/Filelists.mk
index 7e076f34..cb684603 100644
--- a/src/Filelists.mk
+++ b/src/Filelists.mk
@@ -156,6 +156,7 @@ SNMPFILES=$(LWIPDIR)/apps/snmp/snmp_asn1.c \
 	$(LWIPDIR)/apps/snmp/snmp_scalar.c \
 	$(LWIPDIR)/apps/snmp/snmp_table.c \
 	$(LWIPDIR)/apps/snmp/snmp_threadsync.c \
+	$(LWIPDIR)/apps/snmp/snmp_manager.c \
 	$(LWIPDIR)/apps/snmp/snmp_traps.c
 
 # HTTPFILES: HTTP server + client
diff --git a/src/apps/snmp/snmp_manager.c b/src/apps/snmp/snmp_manager.c
new file mode 100644
index 00000000..0f903228
--- /dev/null
+++ b/src/apps/snmp/snmp_manager.c
@@ -0,0 +1,640 @@
+/**
+ * @file
+ * SNMP Manager implementation.
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Christiaan Simons <christiaan.simons@axon.tv>
+ *         Martin Hentschel <info@cl-soft.de>
+ *         Elias Oenal <lwip@eliasoenal.com>
+ *         Harrold Spier (MEP)
+ *
+ */
+
+/*
+** ATTENTION!
+**
+** Function snmp_manager_header_enc() is basically a copy of snmp_prepare_outbound_frame()
+** in snmp_msg.c.
+** Function snmp_send_msg() has much in common with snmp_complete_outbound_frame() in
+** snmp_msg.c.
+**
+** Future updates in one of these two snmp_msg.c functions should be checked and when
+** necessary aligned with both functions here.
+**
+*/
+
+#include "lwip/apps/snmp_opts.h"
+
+#if LWIP_SNMP && SNMP_MNGR
+
+#include <string.h>
+
+#include "lwip/snmp.h"
+#include "lwip/sys.h"
+#include "lwip/apps/snmp.h"
+#include "lwip/apps/snmp_core.h"
+#include "lwip/prot/iana.h"
+#include "snmp_msg.h"
+#include "snmp_asn1.h"
+#include "snmp_core_priv.h"
+#include "lwip/udp.h"
+
+#define SNMP_V3_AUTH_FLAG      0x01
+#define SNMP_V3_PRIV_FLAG      0x02
+
+#define SNMP_MSG_MAX_SIZE      2048
+
+struct snmp_msg_manager
+{
+  /* version */
+  u32_t version;
+  /* community name (zero terminated) */
+  const u8_t *community;
+  /* community string length (exclusive zero term) */
+  u16_t community_strlen;
+  /* request ID */
+  s32_t request_id;
+  /* error status */
+  s32_t error_status;
+  /* error index */
+  s32_t error_index;
+  /* request type */
+  u8_t request_out_type;
+
+#if LWIP_SNMP_V3
+  u8_t *hashedAuthKey;
+  u8_t *hashedPrivKey;
+
+  s32_t msg_id;
+  s32_t msg_max_size;
+  u8_t  msg_flags;
+  s32_t msg_security_model;
+  u8_t  msg_authoritative_engine_id[SNMP_V3_MAX_ENGINE_ID_LENGTH];
+  u8_t  msg_authoritative_engine_id_len;
+  s32_t msg_authoritative_engine_boots;
+  s32_t msg_authoritative_engine_time;
+  u8_t  msg_user_name[SNMP_V3_MAX_USER_LENGTH];
+  u8_t  msg_user_name_len;
+  u8_t  msg_authentication_parameters[SNMP_V3_MAX_AUTH_PARAM_LENGTH];
+  u8_t  msg_authentication_parameters_len;
+  u8_t  msg_privacy_parameters[SNMP_V3_MAX_PRIV_PARAM_LENGTH];
+  u8_t  msg_privacy_parameters_len;
+  u8_t  context_engine_id[SNMP_V3_MAX_ENGINE_ID_LENGTH];
+  u8_t  context_engine_id_len;
+  u8_t  context_name[SNMP_V3_MAX_ENGINE_ID_LENGTH];
+  u8_t  context_name_len;
+#endif
+
+  u16_t outbound_pdu_offset;
+  u16_t outbound_error_status_offset;
+  u16_t outbound_error_index_offset;
+  u16_t outbound_varbind_offset;
+#if LWIP_SNMP_V3
+  u16_t outbound_msg_global_data_offset;
+  u16_t outbound_msg_global_data_end;
+  u16_t outbound_msg_security_parameters_str_offset;
+  u16_t outbound_msg_security_parameters_seq_offset;
+  u16_t outbound_msg_security_parameters_end;
+  u16_t outbound_msg_authentication_parameters_offset;
+  u16_t outbound_scoped_pdu_seq_offset;
+  u16_t outbound_scoped_pdu_string_offset;
+#endif
+};
+
+static err_t snmp_manager_header_enc(struct snmp_msg_manager *request, struct snmp_pbuf_stream *pbuf_stream);
+static err_t snmp_manager_varbind_enc(struct snmp_pbuf_stream *pbuf_stream, struct snmp_varbind *varbinds);
+
+#define BUILD_EXEC(code, retValue) \
+  if ((code) != ERR_OK) { \
+    LWIP_DEBUGF(SNMP_DEBUG, ("SNMP error during creation of outbound frame!: " # code)); \
+    return retValue; \
+  }
+
+#define OF_BUILD_EXEC(code) BUILD_EXEC(code, ERR_ARG)
+
+void *snmp_manager_handle;
+
+/* This request id used to trace feed back */
+static s32_t req_id = 1;
+
+/**
+ * @ingroup snmp_manager
+ * Copy manager message structure to pbuf and sends it
+ * @param request contains the data that should be sent
+ * @param varbinds list of varbinds
+ * @param dip destination IP address
+ * @param port the remote port to which to send the data
+ * @param eth_interface the ethernet interface to use
+ * @return ERR_OK if sending was successful
+ */
+static err_t
+snmp_send_msg(struct snmp_msg_manager *request, struct snmp_varbind *varbinds, ip_addr_t *dip, u16_t port, u16_t eth_interface)
+{
+  struct pbuf *p = NULL;
+  struct snmp_pbuf_stream pbuf_stream;
+  err_t err = ERR_OK;
+
+  /* try allocating pbuf(s) for maximum response size */
+  p = pbuf_alloc(PBUF_TRANSPORT, 1472, PBUF_RAM);
+  if (p == NULL) {
+    return ERR_MEM;
+  }
+
+  snmp_pbuf_stream_init(&pbuf_stream, p, 0, p->tot_len);
+
+  /* LAN1 has if_idx of 2 and LAN2 has if_idx 3: */
+  p->if_idx = eth_interface + 2;
+
+  /* pass 1, encode packet ino the pbuf(s) */
+  OF_BUILD_EXEC( snmp_manager_header_enc(request, &pbuf_stream) );
+  OF_BUILD_EXEC( snmp_manager_varbind_enc(&pbuf_stream, varbinds) );
+
+  {
+    struct snmp_asn1_tlv tlv;
+    u16_t frame_size;
+
+    frame_size = pbuf_stream.offset;
+    u8_t outbound_padding = 0;
+
+#if LWIP_SNMP_V3 && LWIP_SNMP_V3_CRYPTO
+    /* Calculate padding for encryption */
+    if (request->version == SNMP_VERSION_3 && (request->msg_flags & SNMP_V3_PRIV_FLAG)) {
+      u8_t i;
+      outbound_padding = (8 - (u8_t)((frame_size - request->outbound_scoped_pdu_seq_offset) & 0x07)) & 0x07;
+      for (i = 0; i < outbound_padding; i++) {
+        OF_BUILD_EXEC( snmp_pbuf_stream_write(&pbuf_stream, 0) );
+      }
+    }
+#endif
+
+    /* complete missing length in 'Message' sequence ; 'Message' tlv is located at the beginning (offset 0) */
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 3, frame_size + outbound_padding - 1 - 3); /* - type - length_len(fixed, see snmp_prepare_outbound_frame()) */
+    OF_BUILD_EXEC(snmp_pbuf_stream_seek_abs(&pbuf_stream, 0));
+    OF_BUILD_EXEC( snmp_ans1_enc_tlv(&pbuf_stream, &tlv) );
+
+#if LWIP_SNMP_V3
+    if (request->version == SNMP_VERSION_3) {
+      /* complete missing length in 'globalData' sequence */
+      /* - type - length_len(fixed, see snmp_prepare_outbound_frame()) */
+      SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 1, request->outbound_msg_global_data_end
+                               - request->outbound_msg_global_data_offset - 1 - 1);
+      OF_BUILD_EXEC(snmp_pbuf_stream_seek_abs(&pbuf_stream, request->outbound_msg_global_data_offset));
+      OF_BUILD_EXEC(snmp_ans1_enc_tlv(&pbuf_stream, &tlv));
+
+      /* complete missing length in 'msgSecurityParameters' sequence */
+      SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 1, request->outbound_msg_security_parameters_end
+                               - request->outbound_msg_security_parameters_str_offset - 1 - 1);
+      OF_BUILD_EXEC(snmp_pbuf_stream_seek_abs(&pbuf_stream, request->outbound_msg_security_parameters_str_offset));
+      OF_BUILD_EXEC(snmp_ans1_enc_tlv(&pbuf_stream, &tlv));
+
+      SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 1, request->outbound_msg_security_parameters_end
+                               - request->outbound_msg_security_parameters_seq_offset - 1 - 1);
+      OF_BUILD_EXEC(snmp_pbuf_stream_seek_abs(&pbuf_stream, request->outbound_msg_security_parameters_seq_offset));
+      OF_BUILD_EXEC(snmp_ans1_enc_tlv(&pbuf_stream, &tlv));
+
+      // /* complete missing length in scoped PDU sequence */
+      SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 3, frame_size - request->outbound_scoped_pdu_seq_offset - 1 - 3);
+      OF_BUILD_EXEC(snmp_pbuf_stream_seek_abs(&pbuf_stream, request->outbound_scoped_pdu_seq_offset));
+      OF_BUILD_EXEC(snmp_ans1_enc_tlv(&pbuf_stream, &tlv));
+    }
+#endif
+
+    /* complete missing length in 'PDU' sequence */
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, request->request_out_type, 3,
+                             frame_size - request->outbound_pdu_offset - 1 - 3); /* - type - length_len(fixed, see snmp_prepare_outbound_frame()) */
+    OF_BUILD_EXEC( snmp_pbuf_stream_seek_abs(&pbuf_stream, request->outbound_pdu_offset) );
+    OF_BUILD_EXEC( snmp_ans1_enc_tlv(&pbuf_stream, &tlv) );
+
+    /* process and encode final error status */
+    if (request->error_status != 0) {
+      u16_t len;
+      snmp_asn1_enc_s32t_cnt(request->error_status, &len);
+      if (len != 1) {
+        /* error, we only reserved one byte for it */
+        return ERR_ARG;
+      }
+      OF_BUILD_EXEC( snmp_pbuf_stream_seek_abs(&pbuf_stream, request->outbound_error_status_offset) );
+      OF_BUILD_EXEC( snmp_asn1_enc_s32t(&pbuf_stream, len, request->error_status) );
+
+#if 0 // Don't do this when SNMP manager
+      if (request->error_status == SNMP_ERR_TOOBIG) {
+        request->error_index = 0; /* defined by RFC 1157 */
+      } else if (request->error_index == 0) {
+        /* set index to varbind where error occurred (if not already set before, e.g. during GetBulk processing) */
+        request->error_index = request->inbound_varbind_enumerator.varbind_count;
+      }
+#endif
+    }
+
+    /* encode final error index*/
+    if (request->error_index != 0) {
+      u16_t len;
+      snmp_asn1_enc_s32t_cnt(request->error_index, &len);
+      if (len != 1) {
+        /* error, we only reserved one byte for it */
+        return ERR_VAL;
+      }
+      OF_BUILD_EXEC( snmp_pbuf_stream_seek_abs(&pbuf_stream, request->outbound_error_index_offset) );
+      OF_BUILD_EXEC( snmp_asn1_enc_s32t(&pbuf_stream, len, request->error_index) );
+    }
+
+    /* complete missing length in 'VarBindList' sequence ; 'VarBindList' tlv is located directly before varbind offset */
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 3, frame_size - request->outbound_varbind_offset);
+    OF_BUILD_EXEC( snmp_pbuf_stream_seek_abs(&pbuf_stream, request->outbound_varbind_offset - 1 - 3) ); /* - type - length_len(fixed, see snmp_prepare_outbound_frame()) */
+    OF_BUILD_EXEC( snmp_ans1_enc_tlv(&pbuf_stream, &tlv) );
+
+    /* Authenticate response */
+#if LWIP_SNMP_V3 && LWIP_SNMP_V3_CRYPTO
+    /* Encrypt response */
+    if (request->version == SNMP_VERSION_3 && (request->msg_flags & SNMP_V3_PRIV_FLAG)) {
+
+      /* complete missing length in PDU sequence */
+      OF_BUILD_EXEC(snmp_pbuf_stream_seek_abs(&pbuf_stream, request->outbound_scoped_pdu_string_offset));
+      SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 3, frame_size + outbound_padding
+                               - request->outbound_scoped_pdu_string_offset - 1 - 3);
+
+      OF_BUILD_EXEC(snmp_ans1_enc_tlv(&pbuf_stream, &tlv));
+      OF_BUILD_EXEC(snmpv3_crypt(&pbuf_stream, tlv.value_len, request->hashedPrivKey,
+                                 request->msg_privacy_parameters, request->msg_authoritative_engine_boots,
+                                 request->msg_authoritative_engine_time, SNMP_V3_PRIV_ALGO_DES, SNMP_V3_PRIV_MODE_ENCRYPT));
+    }
+
+    if (request->version == SNMP_VERSION_3 && (request->msg_flags & SNMP_V3_AUTH_FLAG)) {
+      u8_t hmac[20];
+
+      OF_BUILD_EXEC(snmp_pbuf_stream_seek_abs(&pbuf_stream, 0));
+      OF_BUILD_EXEC(snmpv3_auth(&pbuf_stream, frame_size + outbound_padding, request->hashedAuthKey, SNMP_V3_AUTH_ALGO_MD5, hmac));
+
+      MEMCPY(request->msg_authentication_parameters, hmac, SNMP_V3_MAX_AUTH_PARAM_LENGTH);
+      OF_BUILD_EXEC(snmp_pbuf_stream_seek_abs(&pbuf_stream,
+                                              request->outbound_msg_authentication_parameters_offset));
+
+      SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 1, SNMP_V3_MAX_AUTH_PARAM_LENGTH);
+      OF_BUILD_EXEC(snmp_ans1_enc_tlv(&pbuf_stream, &tlv));
+      OF_BUILD_EXEC(snmp_asn1_enc_raw(&pbuf_stream,
+                                      request->msg_authentication_parameters, SNMP_V3_MAX_AUTH_PARAM_LENGTH));
+    }
+#endif
+
+    pbuf_realloc(p, frame_size + outbound_padding);
+  }
+
+  /** send to the SNMP agent */
+  err = snmp_sendto(snmp_manager_handle, p, dip, port);
+  pbuf_free(p);
+
+  return err;
+}
+
+/**
+ * @ingroup snmp_manager
+ * Send generic SNMP manager message
+ * @param request contains the data that should be sent
+ * @param varbinds list of varbinds
+ * @param dip destination IP address
+ * @param port the remote port to which to send the data
+ * @param eth_interface the ethernet interface to use
+ * return ERR_OK when success
+ */
+static err_t
+snmp_send_request_generic(struct snmp_msg_manager *request, struct snmp_varbind *varbinds, ip_addr_t *dip, u16_t port, u16_t eth_interface)
+{
+  err_t err = ERR_OK;
+
+  LWIP_ASSERT_SNMP_LOCKED();
+
+  request->request_id = req_id;
+
+  if (!ip_addr_isany(dip)) {
+      /* allocate pbuf, fill it and send it */
+      err = snmp_send_msg(request, varbinds, dip, port, eth_interface);
+  }
+  req_id++;
+  return err;
+}
+
+/**
+ * @ingroup snmp_manager
+ * Send generic SNMP manager message
+ * @param version SNMP version 1, 2 or 3
+ * @param ip address of the destination
+ * @param varbinds list of varbinds
+ * @param ptr_request_id pointer to request id
+ * @param set send set or get request
+ * @param port the remote port to which to send the data
+ * @param eth_interface the ethernet interface to use
+ * @param snmp_community the community used for SNMPv1 and SNMPv2c
+ * @param msg_authoritative_engine_id of the agent, used for SNMPv3
+ * @param msg_authoritative_engine_id_len of the agent, used for SNMPv3
+ * @param msg_authoritative_engine_boots of the agent, used for SNMPv3
+ * @param msg_authoritative_engine_time of the agent, used for SNMPv3
+ * @param auth_flag used for SNMPv3
+ * @param priv_flag used for SNMPv3
+ * @param hashedAuthKey used for SNMPv3
+ * @param hashedPrivKey used for SNMPv3
+ * return ERR_OK when success
+ */
+err_t
+snmp_mngr_send(u8_t version, u32_t ip, struct snmp_varbind *varbinds, s32_t *ptr_request_id,
+               int set, u16_t port, u16_t eth_interface, const char *snmp_community,
+               u8_t *msg_authoritative_engine_id, u8_t msg_authoritative_engine_id_len,
+               u32_t msg_authoritative_engine_boots, u32_t msg_authoritative_engine_time,
+               int auth_flag, int priv_flag, u8_t *hashedAuthKey, u8_t *hashedPrivKey)
+{
+  ip_addr_t dip;
+  err_t err = ERR_OK;
+  struct snmp_msg_manager request = {0};
+
+  request.community = (u8_t *) snmp_community;
+  request.community_strlen = strlen(snmp_community);
+
+  request.msg_authoritative_engine_id_len = msg_authoritative_engine_id_len;
+  memcpy(request.msg_authoritative_engine_id, msg_authoritative_engine_id, LWIP_MIN(msg_authoritative_engine_id_len, sizeof(request.msg_authoritative_engine_id)));
+  request.msg_authoritative_engine_boots = msg_authoritative_engine_boots;
+  request.msg_authoritative_engine_time = msg_authoritative_engine_time + snmpv3_get_engine_time();
+
+  request.msg_user_name_len = strlen(snmp_community);
+  memcpy(request.msg_user_name, snmp_community, LWIP_MIN(request.msg_user_name_len, sizeof(request.msg_user_name)));
+
+  request.hashedAuthKey = hashedAuthKey;
+  request.hashedPrivKey = hashedPrivKey;
+
+  if (set)
+    request.request_out_type = SNMP_ASN1_CLASS_CONTEXT | SNMP_ASN1_CONTENTTYPE_CONSTRUCTED | SNMP_ASN1_CONTEXT_PDU_SET_REQ;
+  else
+    request.request_out_type = SNMP_ASN1_CLASS_CONTEXT | SNMP_ASN1_CONTENTTYPE_CONSTRUCTED | SNMP_ASN1_CONTEXT_PDU_GET_REQ;
+
+  request.msg_max_size = SNMP_MSG_MAX_SIZE;
+
+  request.msg_flags = 0;
+  if (msg_authoritative_engine_id_len)
+  {
+    if (auth_flag) request.msg_flags |= SNMP_V3_AUTH_FLAG;
+    if (priv_flag) request.msg_flags |= SNMP_V3_PRIV_FLAG;
+  }
+
+  *ptr_request_id = req_id;
+
+  request.version = (version == 3) ? SNMP_VERSION_3 : (version == 2) ? SNMP_VERSION_2c : SNMP_VERSION_1;
+  request.msg_security_model = version;
+
+  ip_addr_set_ip4_u32(&dip, ip);
+  {
+    err = snmp_send_request_generic(&request, varbinds, &dip, port, eth_interface);
+  }
+
+  return err;
+}
+
+/**
+ * @ingroup snmp_manager
+ * Encodes varbinds.
+ *
+ * @param pbuf_stream stream used for storing data inside pbuf
+ * @param varbinds linked list of varbinds
+ * @retval err_t ERR_OK if successful, ERR_ARG otherwise
+ */
+static err_t
+snmp_manager_varbind_enc(struct snmp_pbuf_stream *pbuf_stream, struct snmp_varbind *varbinds)
+{
+  struct snmp_varbind *varbind;
+
+  varbind = varbinds;
+
+  while (varbind != NULL) {
+    OF_BUILD_EXEC( snmp_append_outbound_varbind(pbuf_stream, varbind) );
+
+    varbind = varbind->next;
+  }
+
+  return ERR_OK;
+}
+
+/**
+ * @ingroup snmp_manager
+ * Encodes header from head to tail.
+ *
+ * @param request Manager message
+ * @param pbuf_stream stream used for storing data inside pbuf
+ * @retval err_t ERR_OK if successful
+ */
+static err_t
+snmp_manager_header_enc(struct snmp_msg_manager *request, struct snmp_pbuf_stream *pbuf_stream)
+{
+  struct snmp_asn1_tlv tlv;
+
+  /* 'Message' sequence */
+  SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 3, 0);
+  OF_BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+
+  /* version */
+  SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 0);
+  snmp_asn1_enc_s32t_cnt(request->version, &tlv.value_len);
+  OF_BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+  OF_BUILD_EXEC( snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, request->version) );
+
+#if LWIP_SNMP_V3
+  if (request->version < SNMP_VERSION_3) {
+#endif
+    /* community */
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, request->community_strlen);
+    OF_BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+    OF_BUILD_EXEC( snmp_asn1_enc_raw(pbuf_stream, request->community, request->community_strlen) );
+#if LWIP_SNMP_V3
+  } else {
+    const char *id;
+
+    /* globalData */
+    request->outbound_msg_global_data_offset = pbuf_stream->offset;
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 1, 0);
+    OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+
+    /* msgID */
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 1);
+    snmp_asn1_enc_s32t_cnt(request->msg_id, &tlv.value_len);
+    OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+    OF_BUILD_EXEC(snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, request->msg_id));
+
+    /* msgMaxSize */
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 1);
+    snmp_asn1_enc_s32t_cnt(request->msg_max_size, &tlv.value_len);
+    OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+    OF_BUILD_EXEC(snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, request->msg_max_size));
+
+    /* msgFlags */
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, 1);
+    OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+    OF_BUILD_EXEC(snmp_asn1_enc_raw(pbuf_stream, &request->msg_flags, 1));
+
+    /* msgSecurityModel */
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 1);
+    snmp_asn1_enc_s32t_cnt(request->msg_security_model, &tlv.value_len);
+    OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+    OF_BUILD_EXEC(snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, request->msg_security_model));
+
+    /* end of msgGlobalData */
+    request->outbound_msg_global_data_end = pbuf_stream->offset;
+
+    /* msgSecurityParameters */
+    request->outbound_msg_security_parameters_str_offset = pbuf_stream->offset;
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 1, 0);
+    OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+
+    request->outbound_msg_security_parameters_seq_offset = pbuf_stream->offset;
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 1, 0);
+    OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+
+    /* msgAuthoritativeEngineID */
+#if 0 // Don't do this when SNMP manager
+    snmpv3_get_engine_id(&id, &request->msg_authoritative_engine_id_len);
+    MEMCPY(request->msg_authoritative_engine_id, id, request->msg_authoritative_engine_id_len);
+#endif
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, request->msg_authoritative_engine_id_len);
+    OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+    OF_BUILD_EXEC(snmp_asn1_enc_raw(pbuf_stream, request->msg_authoritative_engine_id, request->msg_authoritative_engine_id_len));
+
+#if 0 // Don't do this when SNMP manager
+    request->msg_authoritative_engine_time = snmpv3_get_engine_time();
+    request->msg_authoritative_engine_boots = snmpv3_get_engine_boots();
+#endif
+
+    /* msgAuthoritativeEngineBoots */
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 0);
+    snmp_asn1_enc_s32t_cnt(request->msg_authoritative_engine_boots, &tlv.value_len);
+    OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+    OF_BUILD_EXEC(snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, request->msg_authoritative_engine_boots));
+
+    /* msgAuthoritativeEngineTime */
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 0);
+    snmp_asn1_enc_s32t_cnt(request->msg_authoritative_engine_time, &tlv.value_len);
+    OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+    OF_BUILD_EXEC(snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, request->msg_authoritative_engine_time));
+
+    /* msgUserName */
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, request->msg_user_name_len);
+    OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+    OF_BUILD_EXEC(snmp_asn1_enc_raw(pbuf_stream, request->msg_user_name, request->msg_user_name_len));
+
+#if LWIP_SNMP_V3_CRYPTO
+    /* msgAuthenticationParameters */
+    if (request->msg_flags & SNMP_V3_AUTH_FLAG) {
+      memset(request->msg_authentication_parameters, 0, SNMP_V3_MAX_AUTH_PARAM_LENGTH);
+      request->outbound_msg_authentication_parameters_offset = pbuf_stream->offset;
+      SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 1, SNMP_V3_MAX_AUTH_PARAM_LENGTH);
+      OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+      OF_BUILD_EXEC(snmp_asn1_enc_raw(pbuf_stream, request->msg_authentication_parameters, SNMP_V3_MAX_AUTH_PARAM_LENGTH));
+    } else
+#endif
+    {
+      SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, 0);
+      OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+    }
+
+#if LWIP_SNMP_V3_CRYPTO
+    /* msgPrivacyParameters */
+    if (request->msg_flags & SNMP_V3_PRIV_FLAG) {
+      snmpv3_build_priv_param(request->msg_privacy_parameters);
+
+      SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, SNMP_V3_MAX_PRIV_PARAM_LENGTH);
+      OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+      OF_BUILD_EXEC(snmp_asn1_enc_raw(pbuf_stream, request->msg_privacy_parameters, SNMP_V3_MAX_PRIV_PARAM_LENGTH));
+    } else
+#endif
+    {
+      SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, 0);
+      OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+    }
+
+    /* End of msgSecurityParameters, so we can calculate the length of this sequence later */
+    request->outbound_msg_security_parameters_end = pbuf_stream->offset;
+
+#if LWIP_SNMP_V3_CRYPTO
+    /* For encryption we have to encapsulate the payload in an octet string */
+    if (request->msg_flags & SNMP_V3_PRIV_FLAG) {
+      request->outbound_scoped_pdu_string_offset = pbuf_stream->offset;
+      SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 3, 0);
+      OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+    }
+#endif
+    /* Scoped PDU
+     * Encryption context
+     */
+    request->outbound_scoped_pdu_seq_offset = pbuf_stream->offset;
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 3, 0);
+    OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+
+    /* contextEngineID */
+    snmpv3_get_engine_id(&id, &request->context_engine_id_len);
+    MEMCPY(request->context_engine_id, id, request->context_engine_id_len);
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, request->context_engine_id_len);
+    OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+    OF_BUILD_EXEC(snmp_asn1_enc_raw(pbuf_stream, request->context_engine_id, request->context_engine_id_len));
+
+    /* contextName */
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, request->context_name_len);
+    OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+    OF_BUILD_EXEC(snmp_asn1_enc_raw(pbuf_stream, request->context_name, request->context_name_len));
+  }
+#endif
+
+  /* 'PDU' sequence */
+  request->outbound_pdu_offset = pbuf_stream->offset;
+  SNMP_ASN1_SET_TLV_PARAMS(tlv, request->request_out_type, 3, 0);
+  OF_BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+
+  /* request ID */
+  SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 0);
+  snmp_asn1_enc_s32t_cnt(request->request_id, &tlv.value_len);
+  OF_BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+  OF_BUILD_EXEC( snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, request->request_id) );
+
+  /* error status */
+  SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 1);
+  OF_BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+  request->outbound_error_status_offset = pbuf_stream->offset;
+  OF_BUILD_EXEC( snmp_pbuf_stream_write(pbuf_stream, 0) );
+
+  /* error index */
+  SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 1);
+  OF_BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+  request->outbound_error_index_offset = pbuf_stream->offset;
+  OF_BUILD_EXEC( snmp_pbuf_stream_write(pbuf_stream, 0) );
+
+  /* 'VarBindList' sequence */
+  SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 3, 0);
+  OF_BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+
+  request->outbound_varbind_offset = pbuf_stream->offset;
+
+  return ERR_OK;
+}
+
+#endif /* LWIP_SNMP && SNMP_MNGR */
diff --git a/src/apps/snmp/snmp_msg.c b/src/apps/snmp/snmp_msg.c
index 3d04094e..b0378b8a 100644
--- a/src/apps/snmp/snmp_msg.c
+++ b/src/apps/snmp/snmp_msg.c
@@ -272,7 +272,7 @@ static err_t snmp_process_getnext_request(struct snmp_request *request);
 static err_t snmp_process_getbulk_request(struct snmp_request *request);
 static err_t snmp_process_set_request(struct snmp_request *request);
 
-static err_t snmp_parse_inbound_frame(struct snmp_request *request);
+static err_t snmp_parse_inbound_frame(struct snmp_request *request, u8_t act_as_manager);
 static err_t snmp_prepare_outbound_frame(struct snmp_request *request);
 static err_t snmp_complete_outbound_frame(struct snmp_request *request);
 static void snmp_execute_write_callbacks(struct snmp_request *request);
@@ -282,6 +282,51 @@ static void snmp_execute_write_callbacks(struct snmp_request *request);
 /* implementation */
 /* ----------------------------------------------------------------------- */
 
+#if SNMP_MNGR
+
+snmp_mngr_callback_fct snmp_mngr_callback = NULL;
+void *snmp_mngr_callback_arg;
+
+void
+snmp_set_mngr_callback(snmp_mngr_callback_fct mngr_callback, void* callback_arg)
+{
+  snmp_mngr_callback     = mngr_callback;
+  snmp_mngr_callback_arg = callback_arg;
+}
+
+mngr_find_user_fct mngr_find_user = NULL;
+
+void
+snmp_set_mngr_find_user_fct(mngr_find_user_fct cb)
+{
+	mngr_find_user = cb;
+}
+
+void
+snmp_receive_mngr(void *handle, struct pbuf *p, const ip_addr_t *source_ip, u16_t port)
+{
+  err_t err;
+  struct snmp_request request;
+
+  memset(&request, 0, sizeof(request));
+  request.handle       = handle;
+  request.source_ip    = source_ip;
+  request.source_port  = port;
+  request.inbound_pbuf = p;
+
+  err = snmp_parse_inbound_frame(&request, 1);
+  if (err == ERR_OK) {
+    if ((request.request_type == SNMP_ASN1_CONTEXT_PDU_GET_RESP) || (request.request_type == SNMP_ASN1_CONTEXT_PDU_REPORT)){
+      /* If callback function has been defined call it. */
+      if (snmp_mngr_callback != NULL) {
+        snmp_mngr_callback(&request, snmp_mngr_callback_arg);
+      }
+    }
+  }
+}
+
+#endif
+
 void
 snmp_receive(void *handle, struct pbuf *p, const ip_addr_t *source_ip, u16_t port)
 {
@@ -296,7 +341,7 @@ snmp_receive(void *handle, struct pbuf *p, const ip_addr_t *source_ip, u16_t por
 
   snmp_stats.inpkts++;
 
-  err = snmp_parse_inbound_frame(&request);
+  err = snmp_parse_inbound_frame(&request, 0);
   if (err == ERR_OK) {
     if (request.request_type == SNMP_ASN1_CONTEXT_PDU_GET_RESP) {
       if (request.error_status == SNMP_ERR_NOERROR) {
@@ -324,7 +369,7 @@ snmp_receive(void *handle, struct pbuf *p, const ip_addr_t *source_ip, u16_t por
         }
       }
 #if LWIP_SNMP_V3
-      else {
+      else if (request.version == SNMP_VERSION_3) {
         struct snmp_varbind vb;
 
         vb.next = NULL;
@@ -371,15 +416,15 @@ snmp_receive(void *handle, struct pbuf *p, const ip_addr_t *source_ip, u16_t por
           break;
           default:
             /* Unknown or unhandled error_status */
-            err = ERR_ARG;
+            vb.value = NULL;
         }
 
-        if (err == ERR_OK) {
+        if (vb.value != NULL) {
           snmp_append_outbound_varbind(&(request.outbound_pbuf_stream), &vb);
           request.error_status = SNMP_ERR_NOERROR;
+          request.request_out_type = (SNMP_ASN1_CLASS_CONTEXT | SNMP_ASN1_CONTENTTYPE_CONSTRUCTED | SNMP_ASN1_CONTEXT_PDU_REPORT);
         }
 
-        request.request_out_type = (SNMP_ASN1_CLASS_CONTEXT | SNMP_ASN1_CONTENTTYPE_CONSTRUCTED | SNMP_ASN1_CONTEXT_PDU_REPORT);
         request.request_id = request.msg_id;
       }
 #endif
@@ -787,7 +832,7 @@ snmp_process_set_request(struct snmp_request *request)
  * - ERR_VAL SNMP header is either malformed or rejected
  */
 static err_t
-snmp_parse_inbound_frame(struct snmp_request *request)
+snmp_parse_inbound_frame(struct snmp_request *request, u8_t act_as_manager)
 {
   struct snmp_pbuf_stream pbuf_stream;
   struct snmp_asn1_tlv tlv;
@@ -821,7 +866,7 @@ snmp_parse_inbound_frame(struct snmp_request *request)
 #endif
       )
 #if LWIP_SNMP_CONFIGURE_VERSIONS
-      || (!snmp_version_enabled(s32_value))
+      || (!act_as_manager && !snmp_version_enabled(s32_value))
 #endif
      ) {
     /* unsupported SNMP version */
@@ -835,12 +880,6 @@ snmp_parse_inbound_frame(struct snmp_request *request)
     u16_t u16_value;
     u16_t inbound_msgAuthenticationParameters_offset;
 
-    /* SNMPv3 doesn't use communities */
-    /* @todo: Differentiate read/write access */
-    strncpy((char *)request->community, snmp_community, SNMP_MAX_COMMUNITY_STR_LEN);
-    request->community[SNMP_MAX_COMMUNITY_STR_LEN] = 0; /* ensure NULL termination (strncpy does NOT guarantee it!) */
-    request->community_strlen = (u16_t)strlen((char *)request->community);
-
     /* RFC3414 globalData */
     IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
     IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_SEQUENCE);
@@ -967,130 +1006,136 @@ snmp_parse_inbound_frame(struct snmp_request *request)
      * 3) if msgAuthoritativeEngineID is unknown, zero-length or too long:
          b) https://tools.ietf.org/html/rfc3414#section-7
      */
+    if (!act_as_manager)
     {
-      const char *eid;
-      u8_t eid_len;
+      {
+        const char *eid;
+        u8_t eid_len;
 
-      snmpv3_get_engine_id(&eid, &eid_len);
+        snmpv3_get_engine_id(&eid, &eid_len);
 
-      if ((request->msg_authoritative_engine_id_len == 0) ||
-          (request->msg_authoritative_engine_id_len != eid_len) ||
-          (memcmp(eid, request->msg_authoritative_engine_id, eid_len) != 0)) {
-        snmp_stats.unknownengineids++;
+        if ((request->msg_authoritative_engine_id_len == 0) ||
+            (request->msg_authoritative_engine_id_len != eid_len) ||
+            (memcmp(eid, request->msg_authoritative_engine_id, eid_len) != 0)) {
+          snmp_stats.unknownengineids++;
+          request->msg_flags = 0; /* noauthnopriv */
+          request->error_status = SNMP_ERR_UNKNOWN_ENGINEID;
+          return ERR_OK;
+        }
+      }
+
+      /* 4) verify username */
+      if (snmpv3_get_user((char *)request->msg_user_name, &auth, NULL, &priv, NULL)) {
+        snmp_stats.unknownusernames++;
         request->msg_flags = 0; /* noauthnopriv */
-        request->error_status = SNMP_ERR_UNKNOWN_ENGINEID;
+        request->error_status = SNMP_ERR_UNKNOWN_SECURITYNAME;
         return ERR_OK;
       }
-    }
-
-    /* 4) verify username */
-    if (snmpv3_get_user((char *)request->msg_user_name, &auth, NULL, &priv, NULL)) {
-      snmp_stats.unknownusernames++;
-      request->msg_flags = 0; /* noauthnopriv */
-      request->error_status = SNMP_ERR_UNKNOWN_SECURITYNAME;
-      return ERR_OK;
-    }
 
-    /* 5) verify security level */
-    switch (request->msg_flags & (SNMP_V3_AUTH_FLAG | SNMP_V3_PRIV_FLAG)) {
-      case SNMP_V3_NOAUTHNOPRIV:
-        if ((auth != SNMP_V3_AUTH_ALGO_INVAL) || (priv != SNMP_V3_PRIV_ALGO_INVAL)) {
-          /* Invalid security level for user */
-          snmp_stats.unsupportedseclevels++;
-          request->msg_flags = SNMP_V3_NOAUTHNOPRIV;
-          request->error_status = SNMP_ERR_UNSUPPORTED_SECLEVEL;
-          return ERR_OK;
-        }
-        break;
+      /* 5) verify security level */
+      switch (request->msg_flags & (SNMP_V3_AUTH_FLAG | SNMP_V3_PRIV_FLAG)) {
+        case SNMP_V3_NOAUTHNOPRIV:
+          if ((auth != SNMP_V3_AUTH_ALGO_INVAL) || (priv != SNMP_V3_PRIV_ALGO_INVAL)) {
+            /* Invalid security level for user */
+            snmp_stats.unsupportedseclevels++;
+            request->msg_flags = SNMP_V3_NOAUTHNOPRIV;
+            request->error_status = SNMP_ERR_UNSUPPORTED_SECLEVEL;
+            return ERR_OK;
+          }
+          break;
 #if LWIP_SNMP_V3_CRYPTO
-      case SNMP_V3_AUTHNOPRIV:
-        if ((auth == SNMP_V3_AUTH_ALGO_INVAL) || (priv != SNMP_V3_PRIV_ALGO_INVAL)) {
-          /* Invalid security level for user */
+        case SNMP_V3_AUTHNOPRIV:
+// Disabled the check for (priv != SNMP_V3_PRIV_ALGO_INVAL). This turned out to be necessary
+// for ManageEngine MibBrowser which otherwise complains about an "UnSupported Security Level"
+// during discovery. Not sure yet whether this is an ManageEngine or an lwIP issue.
+          if ((auth == SNMP_V3_AUTH_ALGO_INVAL) /*|| (priv != SNMP_V3_PRIV_ALGO_INVAL) */) {
+            /* Invalid security level for user */
+            snmp_stats.unsupportedseclevels++;
+            request->msg_flags = SNMP_V3_NOAUTHNOPRIV;
+            request->error_status = SNMP_ERR_UNSUPPORTED_SECLEVEL;
+            return ERR_OK;
+          }
+          break;
+        case SNMP_V3_AUTHPRIV:
+          if ((auth == SNMP_V3_AUTH_ALGO_INVAL) || (priv == SNMP_V3_PRIV_ALGO_INVAL)) {
+            /* Invalid security level for user */
+            snmp_stats.unsupportedseclevels++;
+            request->msg_flags = SNMP_V3_NOAUTHNOPRIV;
+            request->error_status = SNMP_ERR_UNSUPPORTED_SECLEVEL;
+            return ERR_OK;
+          }
+          break;
+#endif
+        default:
           snmp_stats.unsupportedseclevels++;
           request->msg_flags = SNMP_V3_NOAUTHNOPRIV;
           request->error_status = SNMP_ERR_UNSUPPORTED_SECLEVEL;
           return ERR_OK;
-        }
-        break;
-      case SNMP_V3_AUTHPRIV:
-        if ((auth == SNMP_V3_AUTH_ALGO_INVAL) || (priv == SNMP_V3_PRIV_ALGO_INVAL)) {
-          /* Invalid security level for user */
-          snmp_stats.unsupportedseclevels++;
+      }
+
+      /* 6) if securitylevel specifies authentication, authenticate message. */
+#if LWIP_SNMP_V3_CRYPTO
+      if (request->msg_flags & SNMP_V3_AUTH_FLAG) {
+        const u8_t zero_arr[SNMP_V3_MAX_AUTH_PARAM_LENGTH] = { 0 };
+        u8_t key[20];
+        u8_t hmac[LWIP_MAX(SNMP_V3_SHA_LEN, SNMP_V3_MD5_LEN)];
+        struct snmp_pbuf_stream auth_stream;
+
+        if (request->msg_authentication_parameters_len > SNMP_V3_MAX_AUTH_PARAM_LENGTH) {
+          snmp_stats.wrongdigests++;
           request->msg_flags = SNMP_V3_NOAUTHNOPRIV;
-          request->error_status = SNMP_ERR_UNSUPPORTED_SECLEVEL;
+          request->error_status = SNMP_ERR_AUTHORIZATIONERROR;
           return ERR_OK;
         }
-        break;
-#endif
-      default:
-        snmp_stats.unsupportedseclevels++;
-        request->msg_flags = SNMP_V3_NOAUTHNOPRIV;
-        request->error_status = SNMP_ERR_UNSUPPORTED_SECLEVEL;
-        return ERR_OK;
-    }
 
-    /* 6) if securitylevel specifies authentication, authenticate message. */
-#if LWIP_SNMP_V3_CRYPTO
-    if (request->msg_flags & SNMP_V3_AUTH_FLAG) {
-      const u8_t zero_arr[SNMP_V3_MAX_AUTH_PARAM_LENGTH] = { 0 };
-      u8_t key[20];
-      u8_t hmac[LWIP_MAX(SNMP_V3_SHA_LEN, SNMP_V3_MD5_LEN)];
-      struct snmp_pbuf_stream auth_stream;
+        /* Rewind stream */
+        IF_PARSE_EXEC(snmp_pbuf_stream_init(&auth_stream, request->inbound_pbuf, 0, request->inbound_pbuf->tot_len));
+        IF_PARSE_EXEC(snmp_pbuf_stream_seek_abs(&auth_stream, inbound_msgAuthenticationParameters_offset));
+        /* Set auth parameters to zero for verification */
+        IF_PARSE_EXEC(snmp_asn1_enc_raw(&auth_stream, zero_arr, request->msg_authentication_parameters_len));
 
-      if (request->msg_authentication_parameters_len > SNMP_V3_MAX_AUTH_PARAM_LENGTH) {
-        snmp_stats.wrongdigests++;
-        request->msg_flags = SNMP_V3_NOAUTHNOPRIV;
-        request->error_status = SNMP_ERR_AUTHORIZATIONERROR;
-        return ERR_OK;
-      }
+        /* Verify authentication */
+        IF_PARSE_EXEC(snmp_pbuf_stream_init(&auth_stream, request->inbound_pbuf, 0, request->inbound_pbuf->tot_len));
 
-      /* Rewind stream */
-      IF_PARSE_EXEC(snmp_pbuf_stream_init(&auth_stream, request->inbound_pbuf, 0, request->inbound_pbuf->tot_len));
-      IF_PARSE_EXEC(snmp_pbuf_stream_seek_abs(&auth_stream, inbound_msgAuthenticationParameters_offset));
-      /* Set auth parameters to zero for verification */
-      IF_PARSE_EXEC(snmp_asn1_enc_raw(&auth_stream, zero_arr, request->msg_authentication_parameters_len));
+        IF_PARSE_EXEC(snmpv3_get_user((char *)request->msg_user_name, &auth, key, NULL, NULL));
+        IF_PARSE_EXEC(snmpv3_auth(&auth_stream, request->inbound_pbuf->tot_len, key, auth, hmac));
 
-      /* Verify authentication */
-      IF_PARSE_EXEC(snmp_pbuf_stream_init(&auth_stream, request->inbound_pbuf, 0, request->inbound_pbuf->tot_len));
-
-      IF_PARSE_EXEC(snmpv3_get_user((char *)request->msg_user_name, &auth, key, NULL, NULL));
-      IF_PARSE_EXEC(snmpv3_auth(&auth_stream, request->inbound_pbuf->tot_len, key, auth, hmac));
-
-      if (memcmp(request->msg_authentication_parameters, hmac, SNMP_V3_MAX_AUTH_PARAM_LENGTH)) {
-        snmp_stats.wrongdigests++;
-        request->msg_flags = SNMP_V3_NOAUTHNOPRIV;
-        request->error_status = SNMP_ERR_AUTHORIZATIONERROR;
-        return ERR_OK;
-      }
-
-      /* 7) if securitylevel specifies authentication, verify engineboots, enginetime and lastenginetime */
-      {
-        s32_t boots = snmpv3_get_engine_boots_internal();
-        if ((request->msg_authoritative_engine_boots != boots) || (boots == 2147483647UL)) {
-          snmp_stats.notintimewindows++;
-          request->msg_flags = SNMP_V3_AUTHNOPRIV;
-          request->error_status = SNMP_ERR_NOTINTIMEWINDOW;
+        if (memcmp(request->msg_authentication_parameters, hmac, SNMP_V3_MAX_AUTH_PARAM_LENGTH)) {
+          snmp_stats.wrongdigests++;
+          request->msg_flags = SNMP_V3_NOAUTHNOPRIV;
+          request->error_status = SNMP_ERR_AUTHORIZATIONERROR;
           return ERR_OK;
         }
-      }
-      {
-        s32_t time = snmpv3_get_engine_time_internal();
-        if (request->msg_authoritative_engine_time > (time + 150)) {
-          snmp_stats.notintimewindows++;
-          request->msg_flags = SNMP_V3_AUTHNOPRIV;
-          request->error_status = SNMP_ERR_NOTINTIMEWINDOW;
-          return ERR_OK;
-        } else if (time > 150) {
-          if (request->msg_authoritative_engine_time < (time - 150)) {
+
+        /* 7) if securitylevel specifies authentication, verify engineboots, enginetime and lastenginetime */
+        {
+          s32_t boots = snmpv3_get_engine_boots_internal();
+          if ((request->msg_authoritative_engine_boots != boots) || (boots == 2147483647UL)) {
+            snmp_stats.notintimewindows++;
+            request->msg_flags = SNMP_V3_AUTHNOPRIV;
+            request->error_status = SNMP_ERR_NOTINTIMEWINDOW;
+            return ERR_OK;
+          }
+        }
+        {
+          s32_t time = snmpv3_get_engine_time_internal();
+          if (request->msg_authoritative_engine_time > (time + 150)) {
             snmp_stats.notintimewindows++;
             request->msg_flags = SNMP_V3_AUTHNOPRIV;
             request->error_status = SNMP_ERR_NOTINTIMEWINDOW;
             return ERR_OK;
+          } else if (time > 150) {
+            if (request->msg_authoritative_engine_time < (time - 150)) {
+              snmp_stats.notintimewindows++;
+              request->msg_flags = SNMP_V3_AUTHNOPRIV;
+              request->error_status = SNMP_ERR_NOTINTIMEWINDOW;
+              return ERR_OK;
+            }
           }
         }
       }
-    }
 #endif
+    }
 
     /* 8) if securitylevel specifies privacy, decrypt message. */
 #if LWIP_SNMP_V3_CRYPTO
@@ -1104,14 +1149,38 @@ snmp_parse_inbound_frame(struct snmp_request *request)
       parent_tlv_value_len -= SNMP_ASN1_TLV_HDR_LENGTH(tlv);
       IF_PARSE_ASSERT(parent_tlv_value_len > 0);
 
-      IF_PARSE_EXEC(snmpv3_get_user((char *)request->msg_user_name, NULL, NULL, &priv, key));
-      if (snmpv3_crypt(&pbuf_stream, tlv.value_len, key,
-                       request->msg_privacy_parameters, request->msg_authoritative_engine_boots,
-                       request->msg_authoritative_engine_time, priv, SNMP_V3_PRIV_MODE_DECRYPT) != ERR_OK) {
-        snmp_stats.decryptionerrors++;
-        request->msg_flags = SNMP_V3_AUTHNOPRIV;
-        request->error_status = SNMP_ERR_DECRYIPTION_ERROR;
-        return ERR_OK;
+#if SNMP_MNGR
+      if (act_as_manager) {
+        u8_t *hashedAuthKey, *hashedPrivKey;
+
+        if (mngr_find_user &&
+            mngr_find_user(request->msg_user_name, request->msg_authoritative_engine_id, request->msg_authoritative_engine_id_len, &hashedAuthKey, &hashedPrivKey)) {
+          if (snmpv3_crypt(&pbuf_stream, tlv.value_len, hashedPrivKey,
+                           request->msg_privacy_parameters, request->msg_authoritative_engine_boots,
+                           request->msg_authoritative_engine_time, SNMP_V3_PRIV_ALGO_DES, SNMP_V3_PRIV_MODE_DECRYPT) != ERR_OK) {
+            request->msg_flags = SNMP_V3_AUTHNOPRIV;
+            request->error_status = SNMP_ERR_DECRYIPTION_ERROR;
+            return ERR_OK;
+          }
+        }
+        else {
+          request->msg_flags = SNMP_V3_AUTHNOPRIV;
+          request->error_status = SNMP_ERR_DECRYIPTION_ERROR;
+          return ERR_OK;
+        }
+      }
+      else
+#endif
+      {
+        IF_PARSE_EXEC(snmpv3_get_user((char *)request->msg_user_name, NULL, NULL, &priv, key));
+        if (snmpv3_crypt(&pbuf_stream, tlv.value_len, key,
+                         request->msg_privacy_parameters, request->msg_authoritative_engine_boots,
+                         request->msg_authoritative_engine_time, priv, SNMP_V3_PRIV_MODE_DECRYPT) != ERR_OK) {
+          snmp_stats.decryptionerrors++;
+          request->msg_flags = SNMP_V3_AUTHNOPRIV;
+          request->error_status = SNMP_ERR_DECRYIPTION_ERROR;
+          return ERR_OK;
+        }
       }
     }
 #endif
@@ -1179,6 +1248,11 @@ snmp_parse_inbound_frame(struct snmp_request *request)
 
   /* validate PDU type */
   switch (tlv.type) {
+#if SNMP_MNGR
+    case (SNMP_ASN1_CLASS_CONTEXT | SNMP_ASN1_CONTENTTYPE_CONSTRUCTED | SNMP_ASN1_CONTEXT_PDU_REPORT):
+      /* Report PDU */
+      break;
+#endif
     case (SNMP_ASN1_CLASS_CONTEXT | SNMP_ASN1_CONTENTTYPE_CONSTRUCTED | SNMP_ASN1_CONTEXT_PDU_GET_REQ):
       /* GetRequest PDU */
       snmp_stats.ingetrequests++;
@@ -1210,29 +1284,36 @@ snmp_parse_inbound_frame(struct snmp_request *request)
   request->request_type = tlv.type & SNMP_ASN1_DATATYPE_MASK;
   request->request_out_type = (SNMP_ASN1_CLASS_CONTEXT | SNMP_ASN1_CONTENTTYPE_CONSTRUCTED | SNMP_ASN1_CONTEXT_PDU_GET_RESP);
 
-  /* validate community (do this after decoding PDU type because we don't want to increase 'inbadcommunitynames' for wrong frame types */
-  if (request->community_strlen == 0) {
-    /* community string was too long or really empty*/
-    snmp_stats.inbadcommunitynames++;
-    snmp_authfail_trap();
-    return ERR_ARG;
-  } else if (request->request_type == SNMP_ASN1_CONTEXT_PDU_SET_REQ) {
-    if (snmp_community_write[0] == 0) {
-      /* our write community is empty, that means all our objects are readonly */
-      request->error_status = SNMP_ERR_NOTWRITABLE;
-      request->error_index  = 1;
-    } else if (strncmp(snmp_community_write, (const char *)request->community, SNMP_MAX_COMMUNITY_STR_LEN) != 0) {
-      /* community name does not match */
-      snmp_stats.inbadcommunitynames++;
-      snmp_authfail_trap();
-      return ERR_ARG;
-    }
-  } else {
-    if (strncmp(snmp_community, (const char *)request->community, SNMP_MAX_COMMUNITY_STR_LEN) != 0) {
-      /* community name does not match */
-      snmp_stats.inbadcommunitynames++;
-      snmp_authfail_trap();
-      return ERR_ARG;
+  if (!act_as_manager)
+  {
+#if LWIP_SNMP_V3
+    if (request->version == SNMP_VERSION_3) {
+      if (request->request_type == SNMP_ASN1_CONTEXT_PDU_SET_REQ) {
+        snmpv3_rw_access_t rw_access;
+        if ((snmpv3_get_user_rw_access((char *)request->msg_user_name, &rw_access) != ERR_OK) ||
+            (rw_access != SNMP_V3_RW_ACCESS)) {
+          /* no write access */
+          request->error_status = SNMP_ERR_NOTWRITABLE;
+          request->error_index  = 1;
+        }
+      }
+    } else
+#endif
+    /* validate community (do this after decoding PDU type because we don't want to increase 'inbadcommunitynames' for wrong frame types */
+    if ((snmp_community_write[0] == 0) ||
+        (strncmp(snmp_community_write, (const char *)request->community, SNMP_MAX_COMMUNITY_STR_LEN) != 0)) {
+      /* no write access. Do we need it? */
+      if ((snmp_community[0] == 0) ||
+         (strncmp(snmp_community, (const char *)request->community, SNMP_MAX_COMMUNITY_STR_LEN) != 0)) {
+        /* both community names do not match */
+        snmp_stats.inbadcommunitynames++;
+        snmp_authfail_trap();
+        return ERR_ARG;
+      } else if (request->request_type == SNMP_ASN1_CONTEXT_PDU_SET_REQ) {
+        /* no write access */
+        request->error_status = SNMP_ERR_NOTWRITABLE;
+        request->error_index  = 1;
+      }
     }
   }
 
@@ -1256,6 +1337,10 @@ snmp_parse_inbound_frame(struct snmp_request *request)
       /* RFC 1905, 4.2.3 */
       request->non_repeaters = 0;
     }
+#if SNMP_MNGR
+  } else if (request->request_type == SNMP_ASN1_CONTEXT_PDU_GET_RESP) {
+    IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &request->error_status));
+#endif
   } else {
     /* only check valid value, don't touch 'request->error_status', maybe a response error status was already set to above; */
     IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &s32_value));
@@ -1274,9 +1359,13 @@ snmp_parse_inbound_frame(struct snmp_request *request)
       /* RFC 1905, 4.2.3 */
       request->max_repetitions = 0;
     }
+#if SNMP_MNGR
+  } else if (request->request_type == SNMP_ASN1_CONTEXT_PDU_GET_RESP) {
+    IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &request->error_index));
+#endif
   } else {
     IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &request->error_index));
-    IF_PARSE_ASSERT(s32_value == 0);
+    IF_PARSE_ASSERT(request->error_index == 0);
   }
 
   /* decode varbind-list type (next container level) */
diff --git a/src/apps/snmp/snmp_msg.h b/src/apps/snmp/snmp_msg.h
index 903f08a2..c639944c 100644
--- a/src/apps/snmp/snmp_msg.h
+++ b/src/apps/snmp/snmp_msg.h
@@ -176,6 +176,16 @@ u8_t snmp_get_local_ip_for_dst(void *handle, const ip_addr_t *dst, ip_addr_t *re
 err_t snmp_varbind_length(struct snmp_varbind *varbind, struct snmp_varbind_len *len);
 err_t snmp_append_outbound_varbind(struct snmp_pbuf_stream *pbuf_stream, struct snmp_varbind *varbind);
 
+#if SNMP_MNGR
+void snmp_receive_mngr(void *handle, struct pbuf *p, const ip_addr_t *source_ip, u16_t port);
+typedef int (*mngr_find_user_fct)(const u8_t *userName, const u8_t *engineId, int engineIdLen, u8_t **, u8_t **);
+void snmp_set_mngr_find_user_fct(mngr_find_user_fct find_user_fct);
+typedef void (*snmp_mngr_callback_fct)(struct snmp_request *request, void* callback_arg);
+void snmp_set_mngr_callback(snmp_mngr_callback_fct inform_manager, void* callback_arg);
+/** handle for sending manager messages */
+extern void *snmp_manager_handle;
+#endif
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/apps/snmp/snmp_traps.c b/src/apps/snmp/snmp_traps.c
index 3f83f2df..17126ad6 100644
--- a/src/apps/snmp/snmp_traps.c
+++ b/src/apps/snmp/snmp_traps.c
@@ -97,7 +97,7 @@ static err_t snmp_trap_header_enc_v1_specific(struct snmp_msg_trap *trap, struct
 static err_t snmp_trap_header_enc_v2c_specific(struct snmp_msg_trap *trap, struct snmp_pbuf_stream *pbuf_stream);
 static err_t snmp_prepare_trap_oid(struct snmp_obj_id *dest_snmp_trap_oid, const struct snmp_obj_id *eoid, s32_t generic_trap, s32_t specific_trap);
 static void snmp_prepare_necessary_msg_fields(struct snmp_msg_trap *trap_msg, const struct snmp_obj_id *eoid, s32_t generic_trap, s32_t specific_trap, struct snmp_varbind *varbinds);
-static err_t snmp_send_msg(struct snmp_msg_trap *trap_msg, struct snmp_varbind *varbinds, u16_t tot_len, ip_addr_t *dip);
+static err_t snmp_send_msg(struct snmp_msg_trap *trap_msg, struct snmp_varbind *varbinds, u16_t tot_len, struct snmp_trap_dst *td);
 
 #define BUILD_EXEC(code) \
   if ((code) != ERR_OK) { \
@@ -296,11 +296,11 @@ snmp_prepare_necessary_msg_fields(struct snmp_msg_trap *trap_msg, const struct s
  * @param trap_msg contains the data that should be sent
  * @param varbinds list of varbinds
  * @param tot_len total length of encoded data
- * @param dip destination IP address
+ * @param td SNMP trap destination
  * @return ERR_OK if sending was successful
  */
 static err_t
-snmp_send_msg(struct snmp_msg_trap *trap_msg, struct snmp_varbind *varbinds, u16_t tot_len, ip_addr_t *dip)
+snmp_send_msg(struct snmp_msg_trap *trap_msg, struct snmp_varbind *varbinds, u16_t tot_len, struct snmp_trap_dst *td)
 {
   err_t err = ERR_OK;
   struct pbuf *p = NULL;
@@ -318,7 +318,7 @@ snmp_send_msg(struct snmp_msg_trap *trap_msg, struct snmp_varbind *varbinds, u16
     snmp_stats.outpkts++;
 
     /** send to the TRAP destination */
-    err = snmp_sendto(snmp_traps_handle, p, dip, LWIP_IANA_PORT_SNMP_TRAP);
+    err = snmp_sendto(snmp_traps_handle, p, &td->dip, LWIP_IANA_PORT_SNMP_TRAP);
     pbuf_free(p);
   } else {
     err = ERR_MEM;
@@ -415,7 +415,7 @@ snmp_send_trap_or_notification_or_inform_generic(struct snmp_msg_trap *trap_msg,
         tot_len = snmp_trap_header_sum(trap_msg, tot_len);
 
         /* allocate pbuf, fill it and send it */
-        err = snmp_send_msg(trap_msg, varbinds, tot_len, &td->dip);
+        err = snmp_send_msg(trap_msg, varbinds, tot_len, td);
       } else {
         /* routing error */
         err = ERR_RTE;
diff --git a/src/include/lwip/apps/snmp_manager.h b/src/include/lwip/apps/snmp_manager.h
new file mode 100644
index 00000000..290737dd
--- /dev/null
+++ b/src/include/lwip/apps/snmp_manager.h
@@ -0,0 +1,61 @@
+/**
+ * @file
+ * SNMP server main API - start and basic configuration
+ */
+
+/*
+ * Copyright (c) 2001, 2002 Leon Woestenberg <leon.woestenberg@axon.tv>
+ * Copyright (c) 2001, 2002 Axon Digital Design B.V., The Netherlands.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Harrold Spier (MEP)
+ *
+ */
+#ifndef LWIP_HDR_APPS_SNMP_MANAGER_H
+#define LWIP_HDR_APPS_SNMP_MANAGER_H
+
+#include "lwip/apps/snmp.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if LWIP_SNMP && SNMP_MNGR
+
+err_t snmp_mngr_send(u8_t version, u32_t ip, struct snmp_varbind *varbinds, s32_t *ptr_request_id,
+                     int set, u16_t port, u16_t eth_interface, const char *snmp_community,
+                     u8_t *msg_authoritative_engine_id, u8_t msg_authoritative_engine_id_len,
+                     u32_t msg_authoritative_engine_boots, u32_t msg_authoritative_engine_time,
+                     int auth_flag, int priv_flag, u8_t *hashedAuthKey, u8_t *hashedPrivKey);
+
+#endif /* LWIP_SNMP && SNMP_MNGR */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_HDR_APPS_SNMP_MANAGER_H */
diff --git a/src/include/lwip/apps/snmp_opts.h b/src/include/lwip/apps/snmp_opts.h
index c892d22a..f602ee69 100644
--- a/src/include/lwip/apps/snmp_opts.h
+++ b/src/include/lwip/apps/snmp_opts.h
@@ -107,6 +107,13 @@
 #define SNMP_TRAP_DESTINATIONS          1
 #endif
 
+/**
+ * SNMP_MNGR: SNMP Manager support
+ */
+#if !defined SNMP_MNGR || defined __DOXYGEN__
+#define SNMP_MNGR        0
+#endif
+
 /**
  * Only allow SNMP write actions that are 'safe' (e.g. disabling netifs is not
  * a safe action and disabled when SNMP_SAFE_REQUESTS = 1).
diff --git a/src/include/lwip/apps/snmpv3.h b/src/include/lwip/apps/snmpv3.h
index e0dda649..72b48d05 100644
--- a/src/include/lwip/apps/snmpv3.h
+++ b/src/include/lwip/apps/snmpv3.h
@@ -67,6 +67,12 @@ typedef enum
   SNMP_V3_USER_STORAGETYPE_READONLY    = 5
 } snmpv3_user_storagetype_t;
 
+typedef enum
+{
+  SNMP_V3_RD_ACCESS = 0,
+  SNMP_V3_RW_ACCESS = 1,
+} snmpv3_rw_access_t;
+
 /*
  * The following callback functions must be implemented by the application.
  * There is a dummy implementation in snmpv3_dummy.c.
@@ -84,6 +90,7 @@ void snmpv3_reset_engine_time(void);
 err_t snmpv3_get_user(const char* username, snmpv3_auth_algo_t *auth_algo, u8_t *auth_key, snmpv3_priv_algo_t *priv_algo, u8_t *priv_key);
 u8_t snmpv3_get_amount_of_users(void);
 err_t snmpv3_get_user_storagetype(const char *username, snmpv3_user_storagetype_t *storagetype);
+err_t snmpv3_get_user_rw_access(const char *username, snmpv3_rw_access_t *rw_access);
 err_t snmpv3_get_username(char *username, u8_t index);
 
 /* The following functions are provided by the SNMPv3 agent */
