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; //先在这里打断点
重新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请求后,可以跳转到断点位置,入下图所示。
切换到调试的选项卡,点击调用堆栈,然后可以看到跳转到谁调用了当前方法。
从左侧列表可以看到如下
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);
这里跳转到 _sess_process_packet_parse_pdu ,结束当前的调试,追加断点,继续下一轮的调试
2.2 PDU怎么解析的
_sess_process_packet_parse_pdu 打断点,F5启动调试,再执行get请求命令后(参考前面2.1.1),切换到调试选项卡,如下所示
相关的代码如下
/*
* 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); //这里打断点,执行到这里
}
打断点后的效果
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的变量,大部分还是默认值。
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单步调试
}
打断点后的效果
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,如下图
完整的代码如下
/*
* 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
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了
之后就是asn_parse_int获取reqid了
直接看执行以后得结果。看群里上面的asn_parse_int是可以直接复用了。后面再逐行解析。
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打断点后运行到这里,效果如下图所示
前面2.2.3中有asn_parse_sequence的代码,这里就不再处理了,直接查看结果
在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处打断点,运行到断点处,效果如下所示
上面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能看到如下
然后查看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,具体的解析后面再看。
继续运行到snmp_parse_var_op的结尾,这里var_name和var_name_len就是绑定的其中一个变量,var_val则是值
2.2.5.2 snmp_set_var_objid
在2.2.5 PDU VarBindList 中的代码中有另外一处断点snmp_set_var_objid,运行到断点处,效果如下所示
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还是没值
忘了一开始从 2.2.2 snmp_parse 追踪 就是只解析值,但是不设置值,所以这里是查不到oid绑定的值的