SNMP++ 04-SNMP中OBJECT IDENTIFIER的BER编码与解码及一些思考

Posted csstormq

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SNMP++ 04-SNMP中OBJECT IDENTIFIER的BER编码与解码及一些思考相关的知识,希望对你有一定的参考价值。

阅读完本文你可以学到:

(1)SNMP 中 OBJECT IDENTIFIER 的 BER 编码与解码代码实现。

(2)在学习 OBJECT IDENTIFIER 编解码过程中的一些思考(思考过后,懂得当涉及对无符号数组进行传输编码时,可以给出一个较佳的方案)。

(3)snmp++-3.3.7 版本中函数 asn_parse_objid 存在的 bug。


一、理论知识

1、Tag

OBJECT IDENTIFIER 对应的 Tag 为 0x06,占用一个八位组。

2、Length

Length有三种形式:定长短格式、定长长格式和变长格式。(SNMP 的所有内置类型的 Length 段都采用定长格式)

定长短格式:采用定长方式,当长度不大于127个八位组时,Length 只在一个八位组中编码。此时,bit0~bit6 表示实际长度,最高位 bit7 为0(用于区分其他两种格式)。

定长长格式:采用定长方式,当长度大于127个八位组时,Length 在多个八位组中编码,此时第一个八位组低七位(bit0~bit6)表示的是 Length 所占的长度,第一个八位组的最高位 bit7 为1,第一个八位组的低七位不能全为 0(用于区分其他两种格式)。

变长格式:采用变长方式,Length 所在八位组固定编码为 0x80,但在 Value 编码结束后以两个 0x00 结尾。这种方式使得可以在编码没有完全结束的情况下,可以先发送部分消息给对方。

3、Value

所有的子标识符(OID 点分十进制中的一位数值)连续编码。(笔者注:内存大小对应的 OID 为 .1.3.6.1.2.1.25.2.2.0,其子标识符自顶到下依次为 1,3,6,1,2,1,25,2,2,0。)

每个子标识符由一个或多个字节组成。子标识符是由一个字节还是多个字节表示的区分规则是:每个字节最高有效位 bit7 表征了该子标识符是否为编码后的最后一个字节。bit7 为 0 表示这个字节是 OID 中最后一个编码字节;bit7 为 1 时表示该字节不是该子标识符最后一个编码(OID没有负值)。正因如此,对于数值大于 127 的子标识符必然要使用多于一个字节的编码。如对于数值为 128(0x80 = 1000 0000B)的子标识符,就应该使用两个字节编码:1000 0001 0000 0000,值为 81 00。

子标识符编码应尽可能地使用字节(也就少一个字节),规定 OID 的第一个子标识符和第二个子标识符经过运算组合后编码为一个字节。该运算法则为:(X * 40)+ Y = Z。

其中 X 表示第一个子标识符,Y 为第二个子标识符。X 的取值为(0,1,2)。另外之所以使用一个“magic number” 为 40,是因为首个子标识符为 0 或 1 的 OID 的第二个子标识符最大的是 39(这种不见得有多优雅的解决方案也确实减少了一个字节的编码等)。这就使得具有 N 个子标识符的 OID,第 i 个(2 <= i <= N  - 1)编码值对应 OID 中的第(i + 1)个子标识符。正是由于上述方程的约束条件,使得方程的结果也是唯一的,即当 Z 为表5-3总第一列所列出的值时,就可以推导出前两个子 OID 的值。

表5-3 OID TLV示例(16 进制)

Z      X     Y
0 <= Z <= 39 0 Z
40 <= Z <= 79 1 Z - 40
Z >= 80 2 Z - 80

二、思考——设计一个包格式,满足“发送端将一个无符号 int 数组中的内容发送到接收端,接收端接收并打印”?

最简单地方案可能是: 其他信息 + 整数1 + 整数2 + . . . + 整数 i + . . . + 整数 n。(其中,“其他信息”在这里的讨论中并不涉及。在这种方案中每个整数 i 占 4 个字节)。

现在增加需求:尽可能地使用字节

思路之一:对每个无符号整数进行压缩,即对每个无符号整数尽可能地只传输其有效位。比如,无符号整数 1 (0x00000001)在原来的传输中要占 4 个字节,现在通过一种规则使得只传输一个字节 0x01。

这种思路是可以减少传输字节的,但同时会带来“不同无符号整数的有效位可能占用不同的字节数”的问题。此时,我们不得不提供额外的信息对无符号整型数组进行划分。

额外的信息是指什么?

方案1(提供额外信息的一种尝试方式,暂不考虑是否可行)为:在每个无符号整数(只含有效位)的后面插入一个特殊标识(至少占一个字节 ),以区分不同的整数。同时,我们需要对与该特殊标识相同的的无符号整数进行“字符填充”类似的工作,以保证透明传输(我们先不考虑该工作的繁琐度)。

假如方案1可行,那么额外信息至少要使用一个字节,这个字节指的就是特殊标识。那么,是否存在更佳方案呢?

是的,看看 SNMP 中是如何对 OID 进行编解码的(见一、理论知识)。不要惊讶,你完全可以把 OID 看作是一个一维无符号整型数组:每个子标识符都不会是负数,相邻子标识符就像数组中的相邻元素。

SNMP 中对 OID 的编码规则可使得:额外信息最多使用一个字节。为什么呢?见表 2-1 。

表 2-1

无符号整数 x 采用 OID 编码规则后占用的八位组个数 额外信息占用的八位组个数
0 <= x < 27 1 0
x = 27 2 1
2< x < 214 2 0
x = 214 3 1
. . . . . . . . .


三、snmp++-3.3.7 版本中函数 asn_parse_objid 存在的 bug
unsigned char *asn_parse_objid(unsigned char *data,
                               int *datalength,
                               unsigned char *type,
                               oid *objid,
                               int *objidlength);
bug 描述:函数 asn_parse_objid 实现中未对 *objidlength 表征的 objid 缓冲大小是否足够进行判断。当 *objidlength 为0 时,直接取用 objid[0],会产生数组访问越界;当 *objidlength 为1 时,直接取用 objid[1],也会产生数组访问越界。

四、代码实现
/************************************************************************/
/* asn1.h                                                                     */
/************************************************************************/
#pragma once

typedef unsigned char u_char;
typedef unsigned long u_long;
typedef u_long oid;

#define MAX_OID_LEN 128
#define MAX_SUBID   0xFFFFFFFF


static u_char* _asn_build_header(u_char *data, size_t *datalength, u_char type, size_t length);
static const u_char* _asn_parse_header(const u_char *data, size_t *datalength, u_char *type, size_t *length);
static u_char* _asn_build_length(u_char *data, size_t *datalength, size_t length);
static const u_char* _asn_parse_length(const u_char *data, size_t *datalength, size_t *length);

u_char* _asn_build_objid(u_char *data, size_t *datalength, u_char type, oid *objid, const size_t objidlength);
const u_char* _asn_parse_objid(const u_char *data, size_t *datalength, u_char *type, oid *objid, size_t *objidlength);

/************************************************************************/
/* asn1.cpp                                                                    */
/************************************************************************/
#include "stdafx.h"
#include "asn1.h"

u_char* _asn_build_header(u_char *data, size_t *datalength, u_char type, size_t length)
{
	if (nullptr == data || nullptr == datalength)
		return nullptr;

	if (*datalength < 1)
		return nullptr;
	*data++ = type;
	--(*datalength);

	return _asn_build_length(data, datalength, length);
}

const u_char* _asn_parse_header(const u_char *data, size_t *datalength, u_char *type, size_t *length)
{
	if (nullptr == data || nullptr == datalength || nullptr == type || nullptr == length)
		return nullptr;

	if (*datalength < 1)
		return nullptr;
	*type = *data++;
	--(*datalength);

	return _asn_parse_length(data, datalength, length);
}

/* 支持的最大长度为65535字节 */
u_char* _asn_build_length(u_char *data, size_t *datalength, size_t length)
{
	if (nullptr == data || nullptr == datalength)
		return nullptr;

	const u_char *initdatap = data;

	if (length < 0x80)
	{   /* 定长短格式 */
		if (*datalength < 1)
			return nullptr;
		*data++ = (u_char)length;
	}
	else if (length <= 0xFF)
	{   /* 定长长格式,长度占用一个八位组 */
		if (*datalength < 2)
			return nullptr;
		*data++ = (u_char)0x81;       /* Length 段的第一个八位组为 10000001,对应的十六进制即为 0x81 */
		*data++ = (u_char)length;     /* 将 length 的低 bit0~bit7 赋值给 *data,并使 data 指向 data 的下一个八位组 */
	}
	else if (length <= 0xFFFF)
	{   /* 定长长格式,长度占用两个八位组 */
		if (*datalength < 3)
			return nullptr;
		*data++ = (u_char)0x82;          /* Length 段的第一个八位组为 10000010,对应的十六进制即为 0x82 */
		*data++ = (u_char)(length >> 8); /* 将 length 的低 bit8~bit15 赋值给 *data,并使 data 指向 data 的下一个八位组 */
		*data++ = (u_char)length;        /* 将 length 的低 bit0~bit7 赋值给 *data,并使 data 指向 data 的下一个八位组 */
	}
	else
	{   /* 长度太长,不支持 */
		return nullptr;
	}

	*datalength -= (data - initdatap);
	return data;
}

const u_char* _asn_parse_length(const u_char *data, size_t *datalength, size_t *length)
{
	if (nullptr == data || nullptr == datalength || nullptr == length)
		return nullptr;

	const u_char *initdatap = data;
	u_char lengthbyte = *data++;

	if (lengthbyte < 0x80)
	{   /* 定长短格式 */
		*length = lengthbyte;
	}
	else if (lengthbyte == 0x80)
	{   /* 0x80 为变长格式,不支持 */
		return nullptr;
	}
	else
	{   /* 定长长格式 */
		size_t bytes = (size_t)(lengthbyte - 0x80);   /* 计算 Length 段占用的八位组个数 */
		if (bytes > sizeof(*length))
			return nullptr;   /* Length 段太长 */

		*length = 0;   /* 消除 *length 的初值可能对 *length 最终结果带来的影响 */
		while (bytes--)
		{
			*length <<= 8;
			*length |= (*data++);
		}
	}

	*datalength -= (data - initdatap);
	return data;
}

u_char* _asn_build_objid(u_char *data, size_t *datalength, u_char type, oid *objid, const size_t objidlength)
{
	if (nullptr == data || nullptr == datalength || nullptr == objid)
		return nullptr;

	u_long first_objid_val;

	if (objidlength == 0)
		first_objid_val = 0;
	else if (objid[0] > 2)
		return nullptr;			/* bad first sub_identifier */
	else if (objidlength == 1)
		first_objid_val = objid[0] * 40;
	else
	{	
		/* second sub_identifier <= 39 when first sub_identifier is 0 or 1 */
		if (objid[1] >= 40 && objid[0] < 2)
			return nullptr;		/* bad second sub_identifier */
		first_objid_val = objid[0] * 40 + objid[1];
	}

	u_char objid_size[MAX_OID_LEN];
	u_long subid = first_objid_val;
	size_t asn_length = 0;

	if (objidlength > sizeof(objid_size) / sizeof(*objid_size))
		return nullptr;

	for (size_t i = 1;;)
	{
		if (subid < (u_long)0x80)
			objid_size[i] = 1, asn_length += 1;
		else if (subid < (u_long)0x4000)
			objid_size[i] = 2, asn_length += 2;
		else if (subid < (u_long)0x200000)
			objid_size[i] = 3, asn_length += 3;
		else if (subid < (u_long)0x10000000)
			objid_size[i] = 4, asn_length += 4;
		else if (subid <= (u_long)0xffffffff)
			objid_size[i] = 5, asn_length += 5;
		else
			return nullptr;			/*  sub_identifier too long */

		if (++i >= objidlength)
			break;
		subid = objid[i];
	}

	if ((data = _asn_build_header(data, datalength, type, asn_length)) == nullptr)
		return nullptr;

	if (*datalength < asn_length)
		return nullptr;

	oid *op = objid + 2;
	for (size_t i = 1, subid = first_objid_val; i < objidlength; ++i)
	{
		if (i != 1)
			subid = *op++;
		switch (objid_size[i])
		{
		case 1:
			*data++ = (u_char)subid;
			break;

		case 2:
			*data++ = (u_char)(subid >> 7 | 0x80);
			*data++ = (u_char)(subid & 0x07f);
			break;

		case 3:
			*data++ = (u_char)(subid >> 14 | 0x80);
			*data++ = (u_char)((subid >> 7 & 0x07f) | 0x80);
			*data++ = (u_char)(subid & 0x07f);
			break;

		case 4:
			*data++ = (u_char)(subid >> 21 | 0x80);
			*data++ = (u_char)((subid >> 14 & 0x07f) | 0x80);
			*data++ = (u_char)((subid >> 7 & 0x07f) | 0x80);
			*data++ = (u_char)(subid & 0x07f);
			break;

		case 5:
			*data++ = (u_char)(subid >> 28 | 0x80);
			*data++ = (u_char)((subid >> 21 & 0x07f) | 0x80);
			*data++ = (u_char)((subid >> 14 & 0x07f) | 0x80);
			*data++ = (u_char)((subid >> 7 & 0x07f) | 0x80);
			*data++ = (u_char)(subid & 0x07f);
			break;

		default:
			return nullptr;
		}
	}

	*datalength -= asn_length;
	return data;
}

const u_char* _asn_parse_objid(const u_char *data, size_t *datalength, u_char *type, oid *objid, size_t *objidlength)
{
	if (nullptr == data || nullptr == datalength || nullptr == type || nullptr == objid || nullptr == objidlength)
		return nullptr;

	const u_char *initdatap = data;
	oid *oidp = objid + 1;
	size_t asn_length;

 	if ((data = _asn_parse_header(data, datalength, type, &asn_length)) == nullptr)
		return nullptr;
	if (*type != 0x06)
		return nullptr;			/* Wrong Type. Not an oid */
	if (asn_length > *datalength)
		return nullptr;			/* data overflow */

	/* Handle invalid object identifier encodings of the form 06 00 robustly */
	if (asn_length == 0)
	{
		if (*objidlength < 2)
			return nullptr;
		objid[0] = objid[1] = 0;
	}
	
	u_long subid;

	for (size_t i = 1; asn_length > 0; ++i)
	{
		subid = 0;
		do 
		{
			subid <<= 7;
			subid |= (u_long)(*data & 0x07f);
			--asn_length;
		} while ((*data++ & 0x80) && asn_length > 0);

		if (subid > MAX_SUBID)
			return nullptr;			/* sub_identifier too long */
		if (i >= *objidlength)
			return nullptr;			/* objid overflow */
		*oidp++ = (oid)subid;
	}

	if (*objidlength < 2)
		return nullptr;		/* objid overflow */
	if (objid[1] < 40)
		objid[0] = 0;
	else if (objid[1] < 80)
		objid[0] = 1, objid[1] -= 40;
	else
		objid[0] = 2, objid[1] -= 80;

	*objidlength = (size_t)(oidp - objid);
	*datalength -= (data - initdatap);
	return data;
}

// snmp_get.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <stdio.h>
#include <iostream>
using namespace std;
#include <string.h>
#include "asn1.h"

void print(const u_char *data, size_t datalength)
{
	if (nullptr == data || datalength < 1)
		return;

	for (size_t i = 0; i < datalength; ++i)
	{
		printf("0x%.2X ", data[i]);
	}
	printf("\n\n");
}

int _tmain(int argc, _TCHAR* argv[])
{
	u_char buf[100];
	size_t validlen = sizeof(buf);
	u_long oid[] = {1,3,6,1,4,1,1048576,110,9,10012340}/*{1, 3, 6, 1, 2, 1, 25, 2, 2, 0}*/;
	if (_asn_build_objid(buf, &validlen, 0x06, oid, sizeof(oid) / sizeof(*oid)) == nullptr)
		return -1;
	print(buf, sizeof(buf)-validlen);

	u_long oid2[20];
	size_t oidlength = sizeof(oid2) / sizeof(*oid2);
	u_char type;
	validlen = sizeof(buf)-validlen;
	if (_asn_parse_objid(buf, &validlen, &type, oid2, &oidlength) == nullptr)
	{
		cout << "_asn_parse_objid error" << endl;
		return -1;
	}

	for (size_t i = 0; i < oidlength; ++i)
	{
		cout << oid2[i] << " ";
	}
	cout << endl;
	cout << "oidlength:" << oidlength << endl;

	return 0;
}

注:“一、理论知识 3. Value”中的内容引自《深入理解Net-Snmp》(张春强著)。


以上是关于SNMP++ 04-SNMP中OBJECT IDENTIFIER的BER编码与解码及一些思考的主要内容,如果未能解决你的问题,请参考以下文章

SNMP4J与ObjectSNMP对比分析之我见

Powershell:将八位字节字符串 (SNMP) 输出转换为十六进制(Mac 地址)

如何在windows7上使用snmp命令

Net-SNMP - 在新的 MIB 模块中编译,无需编译整个 SNMP 代理

linux 服务器怎么开启snmp

SNMP