snmp v1 get请求优化(中)

152 阅读12分钟

1背景

这一篇是snmp v1 get请求优化(上) 的续集1,之前的文章里没找到具体的oid处理,猜测了几个方向,但是定位下来没找到相关的实现。所以有了上一篇的调试snmp v1 get请求优化---调试net-snmp。今天在调试的基础上继续定位。

2分析net-snmp源码

2.1 定位到pdu创建

上一篇定位到了 绑定udp端口下面netsnmp_register_agent_nsap查看实现,在

s->callback = handle_snmp_packet; //先在这里打断点

image.png

重新F5,结果还没等调用就执行到了这里。。。所以这里跳转到绑定方法(见下图)。

2.1.1 断点,调试,模拟GET请求

还得在方法里面打断点,跳转到 handle_snmp_packet 并打断点,重新执行,没有跳转到断点。接下来使用命令模拟get请求

 /usr/local/net-snmp/bin/snmpget -v1 -c public localhost:1161 .1.3.6.1.2.1.1.1.0

模拟get请求后,可以跳转到断点位置,入下图所示。

image.png

切换到调试的选项卡,点击调用堆栈,然后可以看到跳转到谁调用了当前方法。

image.png

从左侧列表可以看到如下

graph LR
main --> receive--> snmp_read2-->snmp_sess_read2-->_sess_read
_sess_read-->_sess_process_packet-->_sess_process_packet_handle_pdu

挨个检查哪一层初始化的pdu,最终在_sess_process_packet内找到了如下代码

_sess_process_packet(void *sessp, netsnmp_session * sp,

struct snmp_internal_session *isp,

netsnmp_transport *transport,

void *opaque, int olength,

u_char * packetptr, int length)

{

struct session_list *slp = (struct session_list *) sessp;

netsnmp_pdu *pdu;

int rc;

  


pdu = _sess_process_packet_parse_pdu(sessp, sp, isp, transport, opaque,

olength, packetptr, length);

if (NULL == pdu)

return -1;

  


/*

* find session to process pdu. usually that will be the current session,

* but with the introduction of shared transports, another session may

* have the same socket.

*/

do {

rc = _sess_process_packet_handle_pdu(sessp, sp, isp, transport, pdu);

image.png

这里跳转到 _sess_process_packet_parse_pdu ,结束当前的调试,追加断点,继续下一轮的调试

image.png

2.2 PDU怎么解析的

_sess_process_packet_parse_pdu 打断点,F5启动调试,再执行get请求命令后(参考前面2.1.1),切换到调试选项卡,如下所示

image.png

相关的代码如下

/*

* This function parses a packet into a PDU

*/

static netsnmp_pdu *

_sess_process_packet_parse_pdu(void *sessp, netsnmp_session * sp,

struct snmp_internal_session *isp,

netsnmp_transport *transport,

void *opaque, int olength,

u_char * packetptr, int length)

{

netsnmp_pdu *pdu;

int ret = 0;

int dump = 0, filter = 0;



  
// 2.2.1 snmp_create_sess_pdu 追踪

if (isp->hook_create_pdu) {

pdu = isp->hook_create_pdu(transport, opaque, olength);

} else {

pdu = snmp_create_sess_pdu(transport, opaque, olength);

}


if (pdu == NULL) {

snmp_log(LOG_ERR, "pdu failed to be created\n");

SNMP_FREE(opaque);

return NULL;

}


  
//2.2.2 snmp_parse 追踪

if (isp->hook_parse) {

ret = isp->hook_parse(sp, pdu, packetptr, length);

} else {

ret = snmp_parse(sessp, sp, pdu, packetptr, length);

}



return pdu;

}

2.2.1 snmp_create_sess_pdu 追踪


if (isp->hook_create_pdu) {

pdu = isp->hook_create_pdu(transport, opaque, olength);

} else {

pdu = snmp_create_sess_pdu(transport, opaque, olength); //这里打断点,执行到这里

}

打断点后的效果

image.png

F11单步调试进入,看到的方法代码如下

netsnmp_pdu *

snmp_create_sess_pdu(netsnmp_transport *transport, void *opaque,

size_t olength)

{

netsnmp_pdu *pdu = (netsnmp_pdu *)calloc(1, sizeof(netsnmp_pdu));

if (pdu == NULL) {

DEBUGMSGTL(("sess_process_packet", "can't malloc space for PDU\n"));

return NULL;

}

  


/*

* Save the transport-level data specific to this reception (e.g. UDP

* source address).

*/

  


pdu->transport_data = opaque;

pdu->transport_data_length = olength;

pdu->tDomain = transport->domain;

pdu->tDomainLen = transport->domain_length;

return pdu;

}

这里看不到有PDU其他处理,还得继续找其他地方,查看pdu的变量,大部分还是默认值。

image.png

2.2.2 snmp_parse 追踪

上面2.2.1没查到pdu的其他信息,还得继续找,然后找到如下位置

if (isp->hook_parse) {

ret = isp->hook_parse(sp, pdu, packetptr, length);

} else {

ret = snmp_parse(sessp, sp, pdu, packetptr, length); //这里打断点,F11单步调试

}

打断点后的效果

image.png

F11单步调试进入,看到的方法代码如下

/**

* Parse a PDU.

* @param slp [in] Session pointer (struct session_list).

* @param pss [in] Session pointer (netsnmp_session).

* @param pdu [out] Parsed PDU.

* @param data [in] PDU to parse.

* @param length [in] Length of data.

*

* @returns 0 upon success; -1 upon failure.

*/

int

snmp_parse(struct session_list *slp,

netsnmp_session * pss,

netsnmp_pdu *pdu, u_char * data, size_t length)

{

int rc;

  


rc = _snmp_parse(slp, pss, pdu, data, length);

if (rc) {

if (!pss->s_snmp_errno) {

pss->s_snmp_errno = SNMPERR_BAD_PARSE;

}

SET_SNMP_ERROR(pss->s_snmp_errno);

}

  


return rc;

}

这里还有一层嵌套,F11 到 _snmp_parse,如下图

image.png

完整的代码如下

/*

* Parses the packet received on the input session, and places the data into

* the input pdu. length is the length of the input packet.

* If any errors are encountered, -1 or USM error is returned.

* Otherwise, a 0 is returned.

*/

static int

_snmp_parse(void *sessp,

netsnmp_session * session,

netsnmp_pdu *pdu, u_char * data, size_t length)

{


//处理版本 2.2.3
if (session->version != SNMP_DEFAULT_VERSION) {

pdu->version = session->version;

} else {

pdu->version = snmp_parse_version(data, length);

}

  


switch (pdu->version) {
case SNMP_VERSION_1:


DEBUGDUMPSECTION("recv", "PDU");
//2.2.4 跳过community,处理后续
result = snmp_pdu_parse(pdu, data, &length);

if (result < 0) {

/*

* This indicates a parse error.

*/

snmp_increment_statistic(STAT_SNMPINASNPARSEERRS);

}

DEBUGINDENTADD(-6);

break;

#endif /* support for community based SNMP */


}

return result;

}

2.2.3 PDU version

版本处理如下代码所示,如果是默认版本,就解析版本号

if (session->version != SNMP_DEFAULT_VERSION) {

pdu->version = session->version;

} else {

pdu->version = snmp_parse_version(data, length);

}

这里可以查看snmp_parse_version解析

/*

* Parses the packet received to determine version, either directly

* from packets version field or inferred from ASN.1 construct.

*/

static int

snmp_parse_version(u_char * data, size_t length)

{

u_char type;

long version = SNMPERR_BAD_VERSION;

  


data = asn_parse_sequence(data, &length, &type,

(ASN_SEQUENCE | ASN_CONSTRUCTOR), "version");

if (data) {

DEBUGDUMPHEADER("recv", "SNMP Version");

data =

asn_parse_int(data, &length, &type, &version, sizeof(version));

DEBUGINDENTLESS();

if (!data || type != ASN_INTEGER) {

return SNMPERR_BAD_VERSION;

}

}

return version;

}

继续查看asn_parse_sequence的解析,这个大量重复使用了

/**

* @internal

* same as asn_parse_header with test for expected type

*

* @see asn_parse_header

*

* @param data IN - pointer to start of object

* @param datalength IN/OUT - number of valid bytes left in buffer

* @param type OUT - asn type of object

* @param expected_type IN expected type

* @return Returns a pointer to the first byte of the contents of this object.

* Returns NULL on any error.

*

*/

u_char *

asn_parse_sequence(u_char * data, size_t * datalength, u_char * type, u_char expected_type, /* must be this type */

const char *estr)

{ /* error message prefix */

data = asn_parse_header(data, datalength, type);

return data;

}

上面的能看到asn_parse_header处理后的data为2 继续前文的 snmp_parse_version解析asn_parse_int,直接看最后的结果 版本解析最后结果如下所示为0

image.png

2.2.4 PDU snmp_pdu_parse 之 reqid

在2.2.3 处理版本后,跳过community(跟版本区别不大),直接看后续的解析snmp_pdu_parse

DEBUGDUMPSECTION("recv", "PDU");

result = snmp_pdu_parse(pdu, data, &length);

if (result < 0) {

/*

* This indicates a parse error.

*/

snmp_increment_statistic(STAT_SNMPINASNPARSEERRS);

}

DEBUGINDENTADD(-6);

break;

这里的snmp_pdu_parse部分代码如下

int

snmp_pdu_parse(netsnmp_pdu *pdu, u_char * data, size_t * length)

{
  
/*

* Get the PDU type

*/

data = asn_parse_header(data, length, &msg_type);

if (data == NULL)

return -1;

DEBUGMSGTL(("dumpv_recv"," Command %s\n", snmp_pdu_type(msg_type)));

pdu->command = msg_type;




/*

* get the fields in the PDU preceding the variable-bindings sequence

*/

switch (pdu->command) {
case SNMP_MSG_RESPONSE:
case SNMP_MSG_GET:


/*

* request id

*/

DEBUGDUMPHEADER("recv", "request_id");
//2.2.4 处理reqid
data = asn_parse_int(data, length, &type, &pdu->reqid,

sizeof(pdu->reqid));

DEBUGINDENTLESS();

if (data == NULL) {

return -1;

}


default:

snmp_log(LOG_ERR, "Bad PDU type received: 0x%.2x\n", pdu->command);

snmp_increment_statistic(STAT_SNMPINASNPARSEERRS);

return -1;

}

  


/*

* get header for variable-bindings sequence

*/

DEBUGDUMPSECTION("recv", "VarBindList");
//2.2.5 处理VarBindList
data = asn_parse_sequence(data, length, &type,

(ASN_SEQUENCE | ASN_CONSTRUCTOR),

"varbinds");

if (data == NULL)

goto fail;



return -1;
}

单步执行到这里能判断到出解析的命令为get了

image.png

之后就是asn_parse_int获取reqid了

image.png

直接看执行以后得结果。看群里上面的asn_parse_int是可以直接复用了。后面再逐行解析。

image.png

2.2.5 PDU VarBindList 之 oid

在2.2.4中snmp_pdu_parse直接找到后续的

/*

* get header for variable-bindings sequence

*/

DEBUGDUMPSECTION("recv", "VarBindList");
//这里打断点,并运行到这里
data = asn_parse_sequence(data, length, &type,

(ASN_SEQUENCE | ASN_CONSTRUCTOR),

"varbinds");

在asn_parse_sequence打断点后运行到这里,效果如下图所示

image.png

前面2.2.3中有asn_parse_sequence的代码,这里就不再处理了,直接查看结果

image.png

在snmp_pdu_parse继续找后续,VarBindList内的处理

/*

* get each varBind sequence

*/

while ((int) *length > 0) {

vp = SNMP_MALLOC_TYPEDEF(netsnmp_variable_list);

if (NULL == vp)

goto fail;

  


vp->name_length = MAX_OID_LEN;

DEBUGDUMPSECTION("recv", "VarBind");
//在这里打断点 2.2.5.1 解析oid
data = snmp_parse_var_op(data, objid, &vp->name_length, &vp->type,

&vp->val_len, &var_val, length);

if (data == NULL)

goto fail;

//再在这里打断点 2.2.5.2 赋值oid
if (snmp_set_var_objid(vp, objid, vp->name_length))

goto fail;

  


len = SNMP_MAX_PACKET_LEN;

DEBUGDUMPHEADER("recv", "Value");

switch ((short) vp->type) {

case ASN_INTEGER:

vp->val.integer = (long *) vp->buf;

vp->val_len = sizeof(long);

p = asn_parse_int(var_val, &len, &vp->type,

(long *) vp->val.integer,

sizeof(*vp->val.integer));

if (!p)

goto fail;

break;

case ASN_COUNTER:

case ASN_GAUGE:

case ASN_TIMETICKS:

case ASN_UINTEGER:

vp->val.integer = (long *) vp->buf;

vp->val_len = sizeof(u_long);

p = asn_parse_unsigned_int(var_val, &len, &vp->type,

(u_long *) vp->val.integer,

vp->val_len);

if (!p)

goto fail;

break;

#ifdef NETSNMP_WITH_OPAQUE_SPECIAL_TYPES

case ASN_OPAQUE_COUNTER64:

case ASN_OPAQUE_U64:

#endif /* NETSNMP_WITH_OPAQUE_SPECIAL_TYPES */

case ASN_COUNTER64:

vp->val.counter64 = (struct counter64 *) vp->buf;

vp->val_len = sizeof(struct counter64);

p = asn_parse_unsigned_int64(var_val, &len, &vp->type,

(struct counter64 *) vp->val.

counter64, vp->val_len);

if (!p)

goto fail;

break;

#ifdef NETSNMP_WITH_OPAQUE_SPECIAL_TYPES

case ASN_OPAQUE_FLOAT:

vp->val.floatVal = (float *) vp->buf;

vp->val_len = sizeof(float);

p = asn_parse_float(var_val, &len, &vp->type,

vp->val.floatVal, vp->val_len);

if (!p)

goto fail;

break;

case ASN_OPAQUE_DOUBLE:

vp->val.doubleVal = (double *) vp->buf;

vp->val_len = sizeof(double);

p = asn_parse_double(var_val, &len, &vp->type,

vp->val.doubleVal, vp->val_len);

if (!p)

goto fail;

break;

case ASN_OPAQUE_I64:

vp->val.counter64 = (struct counter64 *) vp->buf;

vp->val_len = sizeof(struct counter64);

p = asn_parse_signed_int64(var_val, &len, &vp->type,

(struct counter64 *) vp->val.counter64,

sizeof(*vp->val.counter64));

  


if (!p)

goto fail;

break;

#endif /* NETSNMP_WITH_OPAQUE_SPECIAL_TYPES */

case ASN_IPADDRESS:

if (vp->val_len != 4)

goto fail;

/* fallthrough */

case ASN_OCTET_STR:

case ASN_OPAQUE:

case ASN_NSAP:

if (vp->val_len < sizeof(vp->buf)) {

vp->val.string = (u_char *) vp->buf;

} else {

vp->val.string = (u_char *) malloc(vp->val_len);

}

if (vp->val.string == NULL) {

goto fail;

}

p = asn_parse_string(var_val, &len, &vp->type, vp->val.string,

&vp->val_len);

if (!p)

goto fail;

break;

case ASN_OBJECT_ID:

vp->val_len = MAX_OID_LEN;

p = asn_parse_objid(var_val, &len, &vp->type, objid, &vp->val_len);

if (!p)

goto fail;

vp->val_len *= sizeof(oid);

vp->val.objid = (oid *) malloc(vp->val_len);

if (vp->val.objid == NULL) {

goto fail;

}

memmove(vp->val.objid, objid, vp->val_len);

break;

case SNMP_NOSUCHOBJECT:

case SNMP_NOSUCHINSTANCE:

case SNMP_ENDOFMIBVIEW:

case ASN_NULL:

break;

case ASN_BIT_STR:

vp->val.bitstring = (u_char *) malloc(vp->val_len);

if (vp->val.bitstring == NULL) {

goto fail;

}

p = asn_parse_bitstring(var_val, &len, &vp->type,

vp->val.bitstring, &vp->val_len);

if (!p)

goto fail;

break;

default:

snmp_log(LOG_ERR, "bad type returned (%x)\n", vp->type);

goto fail;

break;

}

DEBUGINDENTADD(-4);

  


if (NULL == vplast) {

pdu->variables = vp;

} else {

vplast->next_variable = vp;

}

vplast = vp;

vp = NULL;

}

return 0;
2.2.5.1 snmp_parse_var_op

在上面snmp_parse_var_op处打断点,运行到断点处,效果如下所示

image.png

上面snmp_parse_var_op方法的代码如下所示

/*

* u_char * snmp_parse_var_op(

* u_char *data IN - pointer to the start of object

* oid *var_name OUT - object id of variable

* int *var_name_len IN/OUT - length of variable name

* u_char *var_val_type OUT - type of variable (int or octet string) (one byte)

* int *var_val_len OUT - length of variable

* u_char **var_val OUT - pointer to ASN1 encoded value of variable

* int *listlength IN/OUT - number of valid bytes left in var_op_list

*/

  


u_char *

snmp_parse_var_op(u_char * data,

oid * var_name,

size_t * var_name_len,

u_char * var_val_type,

size_t * var_val_len,

u_char ** var_val, size_t * listlength)

{

u_char var_op_type;

size_t var_op_len = *listlength;

u_char *var_op_start = data;

  


data = asn_parse_sequence(data, &var_op_len, &var_op_type,

(ASN_SEQUENCE | ASN_CONSTRUCTOR), "var_op");

if (data == NULL) {

/*

* msg detail is set

*//* */

return NULL;

}

DEBUGDUMPHEADER("recv", "Name");

data =

asn_parse_objid(data, &var_op_len, &var_op_type, var_name,

var_name_len);

DEBUGINDENTLESS();

if (data == NULL) {

ERROR_MSG("No OID for variable");

return NULL;

}

if (var_op_type !=

(u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_OBJECT_ID))

return NULL;

*var_val = data; /* save pointer to this object */

/*

* find out what type of object this is

*/

data = asn_parse_header(data, &var_op_len, var_val_type);

if (data == NULL) {

ERROR_MSG("No header for value");

return NULL;

}

/*

* XXX no check for type!

*/

*var_val_len = var_op_len;

data += var_op_len;

*listlength -= (int) (data - var_op_start);

return data;

}

F11进入snmp_parse_var_op能看到如下

image.png

然后查看asn_parse_objid的实现代码如下

/**

* @internal

* asn_parse_objid - pulls an object indentifier out of an ASN object identifier type.

*

* On entry, datalength is input as the number of valid bytes following

* "data". On exit, it is returned as the number of valid bytes

* following the beginning of the next object.

*

* "objid" is filled with the object identifier.

*

* Returns a pointer to the first byte past the end

* of this object (i.e. the start of the next object).

* Returns NULL on any error.

*

* @param data IN - pointer to start of object

* @param datalength IN/OUT - number of valid bytes left in buffer

* @param type OUT - asn type of object

* @param objid IN/OUT - pointer to start of output buffer

* @param objidlength IN/OUT - number of sub-id's in objid

*

* @return Returns a pointer to the first byte past the end

* of this object (i.e. the start of the next object).

* Returns NULL on any error.

*

*/

u_char *

asn_parse_objid(u_char * data,

size_t * datalength,

u_char * type, oid * objid, size_t * objidlength)

{

static const char *errpre = "parse objid";

/*

* ASN.1 objid ::= 0x06 asnlength subidentifier {subidentifier}*

* subidentifier ::= {leadingbyte}* lastbyte

* leadingbyte ::= 1 7bitvalue

* lastbyte ::= 0 7bitvalue

*/

register u_char *bufp = data;

register oid *oidp = objid + 1;

register u_long subidentifier;

register long length;

u_long asn_length;

size_t original_length = *objidlength;

  


if (NULL == data || NULL == datalength || NULL == type || NULL == objid) {

ERROR_MSG("parse objid: NULL pointer");

return NULL;

}

  


/** need at least 2 bytes to work with: type, length (which might be 0) */

if (*datalength < 2) {

_asn_short_err(errpre, *datalength, 2);

return NULL;

}

  


*type = *bufp++;

if (*type != ASN_OBJECT_ID) {

_asn_type_err(errpre, *type);

return NULL;

}

bufp = asn_parse_nlength(bufp, *datalength - 1, &asn_length);

if (NULL == bufp) {

_asn_short_err(errpre, *datalength - 1, asn_length);

return NULL;

}

  


*datalength -= (int) asn_length + (bufp - data);

  


DEBUGDUMPSETUP("recv", data, bufp - data + asn_length);

  


/*

* Handle invalid object identifier encodings of the form 06 00 robustly

*/

if (asn_length == 0)

objid[0] = objid[1] = 0;

  


length = asn_length;

(*objidlength)--; /* account for expansion of first byte */

  


while (length > 0 && (*objidlength)-- > 0) {

subidentifier = 0;

do { /* shift and add in low order 7 bits */

subidentifier =

(subidentifier << 7) + (*(u_char *) bufp & ~ASN_BIT8);

length--;

} while ((*(u_char *) bufp++ & ASN_BIT8) && (length > 0)); /* last byte has high bit clear */

  


if (length == 0) {

u_char *last_byte = bufp - 1;

if (*last_byte & ASN_BIT8) {

/* last byte has high bit set -> wrong BER encoded OID */

ERROR_MSG("subidentifier syntax error");

return NULL;

}

}

#if defined(EIGHTBIT_SUBIDS) || (SIZEOF_LONG != 4)

if (subidentifier > MAX_SUBID) {

ERROR_MSG("subidentifier too large");

return NULL;

}

#endif

*oidp++ = (oid) subidentifier;

}

  


if (length || oidp < objid + 1) {

ERROR_MSG("OID length exceeds buffer size");

*objidlength = original_length;

return NULL;

}

  


/*

* The first two subidentifiers are encoded into the first component

* with the value (X * 40) + Y, where:

* X is the value of the first subidentifier.

* Y is the value of the second subidentifier.

*/

subidentifier = oidp - objid >= 2 ? objid[1] : 0;

if (subidentifier == 0x2B) {

objid[0] = 1;

objid[1] = 3;

} else {

if (subidentifier < 40) {

objid[0] = 0;

objid[1] = subidentifier;

} else if (subidentifier < 80) {

objid[0] = 1;

objid[1] = subidentifier - 40;

} else {

objid[0] = 2;

objid[1] = subidentifier - 80;

}

}

  


*objidlength = (int) (oidp - objid);

  


DEBUGMSG(("dumpv_recv", " ObjID: "));

DEBUGMSGOID(("dumpv_recv", objid, *objidlength));

DEBUGMSG(("dumpv_recv", "\n"));

return bufp;

}

从调试结果来看,objid 就是解析成1.3.6.1.2.1.1.1.0,具体的解析后面再看。

image.png

继续运行到snmp_parse_var_op的结尾,这里var_name和var_name_len就是绑定的其中一个变量,var_val则是值

image.png

2.2.5.2 snmp_set_var_objid

在2.2.5 PDU VarBindList 中的代码中有另外一处断点snmp_set_var_objid,运行到断点处,效果如下所示

image.png

F11断点进去,代码如下,看描述,只是初始化,直接运行看结果

/*

* Add object identifier name to SNMP variable.

* If the name is large, additional memory is allocated.

* Returns 0 if successful.

*/

  


int

snmp_set_var_objid(netsnmp_variable_list * vp,

const oid * objid, size_t name_length)

{

size_t len = sizeof(oid) * name_length;

  


if (vp->name != vp->name_loc && vp->name != NULL) {

/*

* Probably previously-allocated "big storage". Better free it

* else memory leaks possible.

*/

free(vp->name);

}

  


/*

* use built-in storage for smaller values

*/

if (len <= sizeof(vp->name_loc)) {

vp->name = vp->name_loc;

} else {

vp->name = (oid *) malloc(len);

if (!vp->name)

return 1;

}

if (objid)

memmove(vp->name, objid, len);

vp->name_length = name_length;

return 0;

}

运行到后面的结果显示value还是没值

image.png

忘了一开始从 2.2.2 snmp_parse 追踪 就是只解析值,但是不设置值,所以这里是查不到oid绑定的值的