SNMP协议
写在前面:
由于项目需求需要设备支持SNMPv2版本协议(当前版本Lwip1.4.1),本来Lwip2.0以上的版本都支持SNMPv1、v2、v3版本协议,但是公司前辈告诉我用Lwip2.0以上时,出现了Lwip做服务器跑WEB频繁刷新页面导致协议栈会挂掉的问题,无耐只能基于当前版本修改。
(注:SNMP协议自行百度了解)
1. 在lwip支持的snmpv1版本上更改并支持v2版本
(以下是用wip1.4.1版本自己修改支持SNMPv2会改动的地方)
1.1. 消息接受部分修改
下面是snmp消息回调函数校验长度,版本,upd类型和团体名
static err_t
snmp_pdu_header_check(struct pbuf *p, u16_t ofs, u16_t pdu_len, u16_t *ofs_ret, struct snmp_msg_pstat *m_stat)
{
err_t derr;
u16_t len, ofs_base;
u8_t len_octets;
u8_t type;
s32_t version;
u8_t quest_type;
ofs_base = ofs;
snmp_asn1_dec_type(p, ofs, &type); //检查头是不是0x30
derr = snmp_asn1_dec_length(p, ofs + 1, &len_octets, &len); //计算数据长度是不是等于参数pdu_len
if ((derr != ERR_OK) ||
(pdu_len != (1 + len_octets + len)) ||
(type != (SNMP_ASN1_UNIV | SNMP_ASN1_CONSTR | SNMP_ASN1_SEQ)))
{
snmp_inc_snmpinasnparseerrs();
return ERR_ARG;
}
ofs += (1 + len_octets);
snmp_asn1_dec_type(p, ofs, &type); //检查数据类型是不是0x02
derr = snmp_asn1_dec_length(p, ofs + 1, &len_octets, &len);
if ((derr != ERR_OK) || (type != (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_INTEG)))
{
/* can't decode or no integer (version) */
snmp_inc_snmpinasnparseerrs();
return ERR_ARG;
}
derr = snmp_asn1_dec_s32t(p, ofs + 1 + len_octets, len, &version); //得到snmp版本
if (derr != ERR_OK)
{
/* can't decode */
snmp_inc_snmpinasnparseerrs();
return ERR_ARG;
}
if (version > 1)
{
/* not version 1 */
snmp_inc_snmpinbadversions();
return ERR_ARG;
}
ofs += (1 + len_octets + len);
snmp_asn1_dec_type(p, ofs, &type); //判断数据类型是不是0x04
derr = snmp_asn1_dec_length(p, ofs + 1, &len_octets, &len); // 数据长度6个,表示数据长度占一个字节
if ((derr != ERR_OK) || (type != (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_OC_STR)))
{
/* can't decode or no octet string (community) */
snmp_inc_snmpinasnparseerrs();
return ERR_ARG;
}
derr = snmp_asn1_dec_raw(p, ofs + 1 + len_octets, len, SNMP_COMMUNITY_STR_LEN, m_stat->community); //得到团体名
if (derr != ERR_OK)
{
snmp_inc_snmpinasnparseerrs();
return ERR_ARG;
}
/* add zero terminator */
len = ((len < (SNMP_COMMUNITY_STR_LEN)) ? (len) : (SNMP_COMMUNITY_STR_LEN));//6
m_stat->community[len] = 0;
m_stat->com_strlen = (u8_t)len;
if (strncmp(snmp_publiccommunity, (const char *)m_stat->community, SNMP_COMMUNITY_STR_LEN) != 0)
{
/** @todo: move this if we need to check more names */
snmp_inc_snmpinbadcommunitynames();
snmp_authfail_trap();
return ERR_ARG;
}
ofs += (1 + len_octets + len);//0d
snmp_asn1_dec_type(p, ofs, &type); //得到请求的类型a0 a1 a2 a3 a4 a5
derr = snmp_asn1_dec_length(p, ofs + 1, &len_octets, &len);//01 1c
if (derr != ERR_OK)
{
snmp_inc_snmpinasnparseerrs();
return ERR_ARG;
}
quest_type = type;
switch(type) //a5
{
case (SNMP_ASN1_CONTXT | SNMP_ASN1_CONSTR | SNMP_ASN1_PDU_GET_REQ):
/* GetRequest PDU */
snmp_inc_snmpingetrequests();
derr = ERR_OK;
break;
case (SNMP_ASN1_CONTXT | SNMP_ASN1_CONSTR | SNMP_ASN1_PDU_GET_NEXT_REQ):
/* GetNextRequest PDU */
snmp_inc_snmpingetnexts();
derr = ERR_OK;
break;
case (SNMP_ASN1_CONTXT | SNMP_ASN1_CONSTR | SNMP_ASN1_PDU_GET_RESP):
/* GetResponse PDU */
snmp_inc_snmpingetresponses();
derr = ERR_ARG;
break;
case (SNMP_ASN1_CONTXT | SNMP_ASN1_CONSTR | SNMP_ASN1_PDU_SET_REQ):
/* SetRequest PDU */
snmp_inc_snmpinsetrequests();
derr = ERR_OK;
break;
case (SNMP_ASN1_CONTXT | SNMP_ASN1_CONSTR | SNMP_ASN1_PDU_TRAP):
/* Trap PDU */
snmp_inc_snmpintraps();
derr = ERR_ARG;
break;
case (SNMP_ASN1_CONTXT | SNMP_ASN1_CONSTR | SNMP_ASN1_PDU_GET_BULK):
/* getblik PDU */
snmp_inc_snmpingetbluk();
log_debug("get bluk!!!\r\n", type);
derr = ERR_OK;
break;
default:
snmp_inc_snmpinasnparseerrs();
derr = ERR_ARG;
break;
}
if (derr != ERR_OK)
{
/* unsupported input PDU for this agent (no parse error) */
return ERR_ARG;
}
m_stat->rt = type & 0x1F; //00
ofs += (1 + len_octets);//0f
if (len != (pdu_len - (ofs - ofs_base)))//2b-0f-00 = 1c
{
/* decoded PDU length does not equal actual payload length */
snmp_inc_snmpinasnparseerrs();
return ERR_ARG;
}
snmp_asn1_dec_type(p, ofs, &type); //0x02
derr = snmp_asn1_dec_length(p, ofs + 1, &len_octets, &len); //01 04 4个长度的requestid
if ((derr != ERR_OK) || (type != (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_INTEG)))
{
/* can't decode or no integer (request ID) */
snmp_inc_snmpinasnparseerrs();
return ERR_ARG;
}
derr = snmp_asn1_dec_s32t(p, ofs + 1 + len_octets, len, &m_stat->rid); //得到requestid
if (derr != ERR_OK)
{
/* can't decode */
snmp_inc_snmpinasnparseerrs();
return ERR_ARG;
}
ofs += (1 + len_octets + len);//15
snmp_asn1_dec_type(p, ofs, &type); //02
derr = snmp_asn1_dec_length(p, ofs + 1, &len_octets, &len);
if ((derr != ERR_OK) || (type != (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_INTEG)))
{
/* can't decode or no integer (error-status) */
snmp_inc_snmpinasnparseerrs();
return ERR_ARG;
}
/* must be noError (0) for incoming requests.
log errors for mib-2 completeness and for debug purposes */
derr = snmp_asn1_dec_s32t(p, ofs + 1 + len_octets, len, &m_stat->error_status);//得到错误状态
if (derr != ERR_OK)
{
/* can't decode */
snmp_inc_snmpinasnparseerrs();
return ERR_ARG;
}
switch (m_stat->error_status)
{
case SNMP_ES_TOOBIG:
snmp_inc_snmpintoobigs();
break;
case SNMP_ES_NOSUCHNAME:
snmp_inc_snmpinnosuchnames();
break;
case SNMP_ES_BADVALUE:
snmp_inc_snmpinbadvalues();
break;
case SNMP_ES_READONLY:
snmp_inc_snmpinreadonlys();
break;
case SNMP_ES_GENERROR:
snmp_inc_snmpingenerrs();
break;
}
ofs += (1 + len_octets + len);
if(quest_type < 0xa5)
{
snmp_asn1_dec_type(p, ofs, &type);
derr = snmp_asn1_dec_length(p, ofs + 1, &len_octets, &len);
if ((derr != ERR_OK) || (type != (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_INTEG)))
{
/* can't decode or no integer (error-index) */
snmp_inc_snmpinasnparseerrs();
return ERR_ARG;
}
/* must be 0 for incoming requests.
decode anyway to catch bad integers (and dirty tricks) */
derr = snmp_asn1_dec_s32t(p, ofs + 1 + len_octets, len, &m_stat->error_index); //得到index的错误
if (derr != ERR_OK)
{
/* can't decode */
snmp_inc_snmpinasnparseerrs();
return ERR_ARG;
}
ofs += (1 + len_octets + len);
}
else if(quest_type == 0xa5)
{
snmp_asn1_dec_type(p, ofs, &type);
derr = snmp_asn1_dec_length(p, ofs + 1, &len_octets, &len);
if ((derr != ERR_OK) || (type != (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_INTEG)))
{
/* can't decode or no integer (error-index) */
snmp_inc_snmpinasnparseerrs();
return ERR_ARG;
}
/* must be 0 for incoming requests.
decode anyway to catch bad integers (and dirty tricks) */
snmp_asn1_dec_s32t(p, ofs + 1 + len_octets, len, &m_stat->max_len); //得到最大长度
m_stat->error_index = 0;
ofs += (1 + len_octets + len);
log_debug("get bluk len=%d\r\n", m_stat->max_len);
}
*ofs_ret = ofs;
return ERR_OK;
}
对以上部分进行修改:
- 1.v1版本的snmp协议的upd类型只有a0、a1、a2、a3、a4;而v2版本多了一个a5类型所以我们要在switch语句里加入a5处理;
case (SNMP_ASN1_CONTXT | SNMP_ASN1_CONSTR | SNMP_ASN1_PDU_GET_BULK):
/* getblik PDU */
snmp_inc_snmpingetbluk();
log_debug("get bluk!!!\r\n", type);
derr = ERR_OK;
break;
对照a0到a4类型写个变量记录bluk次数
void snmp_inc_snmpingetbluk(void)
{
snmpingetbluk++;
}
- 2.v2版本的报文是没有index错误的,而v1版本的index错误这个字段变成了get bluk的最大请求个数,我们用个变量把他保存下来。
if(quest_type < 0xa5)
{
snmp_asn1_dec_type(p, ofs, &type);
....
....
ofs += (1 + len_octets + len);
}
else if(quest_type == 0xa5)
{
snmp_asn1_dec_type(p, ofs, &type);
...
...
snmp_asn1_dec_s32t(p, ofs + 1 + len_octets, len, &m_stat->max_len); //得到最大长度
m_stat->error_index = 0;
ofs += (1 + len_octets + len);
log_debug("get bluk len=%d\r\n", m_stat->max_len);
}
- 3.我们加了个get bluk状态,这段代码我们也要加个msg_ps->rt != SNMP_ASN1_PDU_GET_BULK的判断。
if ((err_ret != ERR_OK) ||
((msg_ps->rt != SNMP_ASN1_PDU_GET_REQ) &&
(msg_ps->rt != SNMP_ASN1_PDU_GET_NEXT_REQ) &&
(msg_ps->rt != SNMP_ASN1_PDU_GET_BULK) &&
(msg_ps->rt != SNMP_ASN1_PDU_SET_REQ)) ||
((msg_ps->error_status != SNMP_ES_NOERROR) ||
(msg_ps->error_index != 0)) )
1.2. 消息处理部分修改
这是根据udp类型对数据进行不同回调,我们依葫芦画瓢加入get bulk处理部分
void
snmp_msg_event(u8_t request_id)
{
struct snmp_msg_pstat *msg_ps;
if (request_id < SNMP_CONCURRENT_REQUESTS)
{
msg_ps = &msg_input_list[request_id];
if (msg_ps->rt == SNMP_ASN1_PDU_GET_NEXT_REQ)
{
snmp_msg_getnext_event(request_id, msg_ps);
}
else if (msg_ps->rt == SNMP_ASN1_PDU_GET_REQ)
{
snmp_msg_get_event(request_id, msg_ps);
}
else if(msg_ps->rt == SNMP_ASN1_PDU_SET_REQ)
{
snmp_msg_set_event(request_id, msg_ps);
}
else if(msg_ps->rt == SNMP_ASN1_PDU_GET_BULK)
{
log_debug("a5SNMP_ASN1_PDU_GET_BULK\r\n");
snmp_msg_get_bluk_event(request_id, msg_ps);
}
}
}
snmp_msg_get_bluk_event这个函数就是我们要实现的最难部分,首先我们要知道get bulk的原理,他是从给定的oid开始依次请求一直请求到他给定的最大值;从这里不难发现他其实和get next这个指令很像呢,但是get next是一问一答模式而get bulk只会发一次,所以我们是不是写个for循环调get next指令就行了。
static void
snmp_msg_get_bluk_event(u8_t request_id, struct snmp_msg_pstat *msg_ps)
{
LWIP_DEBUGF(SNMP_MSG_DEBUG, ("snmp_msg_get_event: msg_ps->state==%"U16_F"\n", (u16_t)msg_ps->state));
while ((msg_ps->state == SNMP_MSG_SEARCH_OBJ) &&
(msg_ps->vb_idx < msg_ps->invb.count))//01
{
struct mib_node *mn;
if (msg_ps->vb_idx == 0)
{
msg_ps->vb_ptr = msg_ps->invb.head;
}
else
{
msg_ps->vb_ptr = msg_ps->vb_ptr->next;
}
struct snmp_obj_id oid;
struct snmp_varbind *vb[32];
struct snmp_varbind *recv_vb;
struct obj_def object_def;
for(int i = 0; i < msg_ps->max_len; i++)
{
if (snmp_iso_prefix_expand(msg_ps->vb_ptr->ident_len, msg_ps->vb_ptr->ident, &oid))
{
if (msg_ps->vb_ptr->ident_len > 3)
{
mn = snmp_expand_tree((struct mib_node *)&internet, msg_ps->vb_ptr->ident_len - 4, msg_ps->vb_ptr->ident + 4, &oid);
}
else
{
mn = snmp_expand_tree((struct mib_node *)&internet, 0, NULL, &oid);
}
}
else
{
mn = NULL;
}
if(mn != NULL)
{
msg_ps->state = SNMP_MSG_INTERNAL_GET_OBJDEF;
mn->get_object_def(1, &oid.id[oid.len - 1], &object_def);
vb[i] = snmp_varbind_alloc(&oid, object_def.asn_type, (u8_t)object_def.v_len);
if (vb[i] != NULL)
{
msg_ps->state = SNMP_MSG_INTERNAL_GET_VALUE;
mn->get_value(&object_def, object_def.v_len, vb[i]->value);
snmp_varbind_tail_add(&msg_ps->outvb, vb[i]);
msg_ps->state = SNMP_MSG_SEARCH_OBJ;
msg_ps->vb_idx = 1;
}
else
{
log_debug("snmp_recv couldn't allocate outvb space\r\n");
snmp_error_response(msg_ps, SNMP_ES_TOOBIG);
msg_ps->vb_idx = 1;
}
}
snmp_varbind_list_free(&msg_ps->invb);
recv_vb = snmp_varbind_alloc(&oid, 5, 0);
snmp_varbind_tail_add(&msg_ps->invb, recv_vb);
}
if(mn == NULL)
{
log_debug("mn is null\r\n");
snmp_error_response(msg_ps, SNMP_ES_NOSUCHNAME);
}
}
if ((msg_ps->state == SNMP_MSG_SEARCH_OBJ) &&
(msg_ps->vb_idx == msg_ps->invb.count))
{
err_t err_ret;
err_ret = snmp_send_response(msg_ps);
if (err_ret == ERR_MEM)
{
log_debug("error\r\n");
}
else
{
LWIP_DEBUGF(SNMP_MSG_DEBUG, ("snmp_msg_event = %"S32_F"\n", msg_ps->error_status));
}
snmp_varbind_list_free(&msg_ps->invb);
snmp_varbind_list_free(&msg_ps->outvb);
msg_ps->state = SNMP_MSG_EMPTY;
}
}
上面就是参照get next写的get bulk,至此消息处理部分就完了!
…
1.3. 消息发送部分
至于消息发送部分不需要改动,读源码可以看出他每次读取回来的oid数据是保存在一个链表上的,每次发送消息他会自动把链表上的数据全部自动打包然后发送。
1.4. 细节注意
- 注意SNMP的内存的申请,因项目需求get bluk最大数量会达到32个,而在snmp_msg_get_bluk_event函数里的的vb以及vb结构体里的value和ident都是动态申请的。
在memp_std.h里可以看见snmp的vb和value最大才能申请10个所以我们需要更改
修改后:
我最大请求是32个,为啥 宏定义是33和64?
因为vb的out的确是32个但是在接受消息时候vb的in也是从这里申请的
value为啥是64呢,在下图中可以看出ident和value都是从 MEMP_NUM_SNM_VALUE这个里面申请的所以这个值应该为32*2
struct snmp_varbind *
snmp_varbind_alloc(struct snmp_obj_id *oid, u8_t type, u8_t len)
{
struct snmp_varbind *vb;
vb = (struct snmp_varbind *)memp_malloc(MEMP_SNMP_VARBIND);
// log_debug("vb=%p\r\n",vb);
if (vb != NULL)
{
u8_t i;
vb->next = NULL;
vb->prev = NULL;
i = oid->len;
vb->ident_len = i;
if (i > 0)
{
LWIP_ASSERT("SNMP_MAX_TREE_DEPTH is configured too low", i <= SNMP_MAX_TREE_DEPTH);
/* allocate array of s32_t for our object identifier */
vb->ident = (s32_t *)memp_malloc(MEMP_SNMP_VALUE);
if (vb->ident == NULL)
{
LWIP_DEBUGF(SNMP_MSG_DEBUG, ("snmp_varbind_alloc: couldn't allocate ident value space\r\n"));
log_debug("snmp_varbind_alloc: couldn't allocate ident value space\r\n");
memp_free(MEMP_SNMP_VARBIND, vb);
return NULL;
}
while(i > 0)
{
i--;
vb->ident[i] = oid->id[i];
}
}
else
{
/* i == 0, pass zero length object identifier */
vb->ident = NULL;
}
vb->value_type = type;
vb->value_len = len;
if (len > 0)
{
LWIP_ASSERT("SNMP_MAX_OCTET_STRING_LEN is configured too low", vb->value_len <= SNMP_MAX_VALUE_SIZE);
/* allocate raw bytes for our object value */
vb->value = memp_malloc(MEMP_SNMP_VALUE);
if (vb->value == NULL)
{
LWIP_DEBUGF(SNMP_MSG_DEBUG, ("snmp_varbind_alloc: couldn't allocate value space\n"));
log_debug("snmp_varbind_alloc: couldn't allocate value space\r\n");
if (vb->ident != NULL)
{
memp_free(MEMP_SNMP_VALUE, vb->ident);
}
memp_free(MEMP_SNMP_VARBIND, vb);
return NULL;
}
}
else
{
/* ASN1_NUL type, or zero length ASN1_OC_STR */
vb->value = NULL;
}
}
else
{
LWIP_DEBUGF(SNMP_MSG_DEBUG, ("snmp_varbind_alloc: couldn't allocate varbind space\n"));
}
return vb;
}