Lwip的SNMP协议修改支持V2

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;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值