2026/4/16 15:13:17
网站建设
项目流程
建站出海,深圳个人如何做网站设计,高端网站开发,wordpress 调用最新文章最近遇到了snmp引擎id冲突的问题#xff0c;当所管理交换机之间引擎id冲突的时候#xff0c;全局用户列表#xff08;struct usmUser *puserList#xff09;和#xff08;struct enginetime_struct etimelist#xff09;会有问题。网上针对这块的资料少之又少#xff0c…最近遇到了snmp引擎id冲突的问题当所管理交换机之间引擎id冲突的时候全局用户列表struct usmUser *puserList和struct enginetime_struct etimelist会有问题。网上针对这块的资料少之又少so 在此记录下gdb调试以及阅读net-snmp源码所得出来的【关于net-snmp中engineid冲突时全局etimelist的赋值情况】如有错误敬请指出一、表结构enginetime_structtypedef struct enginetime_struct { u_char *engineID; u_int engineID_len; u_int engineTime; u_int engineBoot; /* * Time boots values received from last authenticated * * message within the previous time window. */ time_t lastReceivedEngineTime; /* * Timestamp made when engineTime/engineBoots was last * * updated. Measured in seconds. */ #ifdef LCD_TIME_SYNC_OPT u_int authenticatedFlag; #endif struct enginetime_struct *next; } enginetime , *Enginetime;1engineID是设备唯一标识可以认为是snmpv3通信中设备的身份证必须是全网唯一的在rfc3414、rfc3411均有相关信息可自行查询2engineBoot是设备snmp重启的次数3engineTime是设备snmp启动后的相对时间误差在USM_TIME_WINDOW 150s详情可见usm_check_and_update_timeliness函数中有判断/* * Boots is ok, see if the boots is the same but the time * is old. */ if (theirBoots boots_uint time_uint theirLastTime) { if (time_difference USM_TIME_WINDOW) { DEBUGMSGTL((usm, %s\n, Message too old.)); *error SNMPERR_USM_NOTINTIMEWINDOW; return -1; } else { /* Old, but acceptable */ *error SNMPERR_SUCCESS; return 0; } }4lastReceivedEngineTime记录上次接受到的用于计算窗口时间差值是否在150s以内5authenticatedFlag是否是权威引擎ID正常认证通过才算看了下只有在执行完usm_check_and_update_timeliness()检查通过之后才会更新的是否将此值设置为Trueusm_process_in_msg函数内部调用snmpusm.c /* * Perform the timeliness/time manager functions. */ if (secLevel SNMP_SEC_LEVEL_AUTHNOPRIV || secLevel SNMP_SEC_LEVEL_AUTHPRIV) { if (usm_check_and_update_timeliness(secEngineID, *secEngineIDLen, boots_uint, time_uint, error) -1) { goto err; } } usm_check_and_update_timeliness函数内部调用snmpusm.c /* * Message is ok, either boots has been advanced, or * time is greater than before with the same boots. */ if (set_enginetime(secEngineID, secEngineIDLen, boots_uint, time_uint, TRUE) ! SNMPERR_SUCCESS) { DEBUGMSGTL((usm, %s\n, Failed updating remote boot/time.)); *error SNMPERR_USM_GENERICERROR; return -1; }二、引擎ID探测过程梳理2.1 会话设置session.flagssnmp_sess_init()函数是初始化一个session里面的flags初始化是设置的不自动探测引擎ID当我们使用snmpv3的时候如果没有取消这个flagssnmp_open是就不会执行探测引擎ID操作此操作将会在之后的实际工作中比如snmp_send的时候调用因为snmp_open-snmp_sess_add结束的时候会将此flags清空掉。具体代码如下snmp_sess_add_ex函数snmp_api.c if (slp-session-version SNMP_VERSION_3) { DEBUGMSGTL((snmp_sess_add, adding v3 session -- maybe engineID probe now\n)); if (!snmpv3_engineID_probe(slp, slp-session)) { DEBUGMSGTL((snmp_sess_add, engine ID probe failed\n)); snmp_sess_close(slp); return NULL; } } slp-session-flags ~SNMP_FLAGS_DONT_PROBE; snmpv3_engineID_probe代码snmp_api.c int snmpv3_engineID_probe(struct session_list *slp, netsnmp_session * in_session) { netsnmp_session *session; int status; struct snmp_secmod_def *sptr NULL; if (slp NULL || slp-session NULL) { return 0; } session slp-session; netsnmp_assert_or_return(session ! NULL, 0); sptr find_sec_mod(session-securityModel); /* * If we are opening a V3 session and we dont know engineID we must probe * it -- this must be done after the session is created and inserted in the * list so that the response can handled correctly. */ if (session-version SNMP_VERSION_3 (0 (session-flags SNMP_FLAGS_DONT_PROBE))) { if (NULL ! sptr NULL ! sptr-probe_engineid) { DEBUGMSGTL((snmp_api, probing for engineID using security model callback...\n)); /* security model specific mechanism of determining engineID */ status (*sptr-probe_engineid) (slp, in_session); if (status ! SNMPERR_SUCCESS) return 0; } else { /* XXX: default to the default RFC5343 contextEngineID Probe? */ return 0; } } /* * see if there is a hook to call now that were done probing for an * engineID */ if (sptr sptr-post_probe_engineid) { status (*sptr-post_probe_engineid)(slp, in_session); if (status ! SNMPERR_SUCCESS) return 0; } return 1; }2.2 回调注册snmpsm_init.h程序初始化都会调用init_snmp此函数调用了init_snmpv3函数这是初始化snmpv3相关内容的其中init_secmod()函数内部的#include snmpsm_init.h是通过configure生成的头文件里面会注册回调函数decode、probe_engineid、post_probe_engineid等。init_snmp init_snmpv3 init_secmod #include snmpsm_init.h由configure生成 以下是net-snmp代码内容 void init_secmod(void) { snmp_register_callback(SNMP_CALLBACK_LIBRARY, SNMP_CALLBACK_SESSION_INIT, set_default_secmod, NULL); netsnmp_ds_register_config(ASN_OCTET_STR, snmp, defSecurityModel, NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_SECMODEL); /* * this file is generated by configure for all the stuff were using */ #include snmpsm_init.h }2.3 探测流程2.3.1 调用探测初始化会话snmp_sess_init() session-flags ~SNMP_FLAGS_DONT_PROBE打开会话snmp_open snmp_sess_open _sess_open添加会话snmp_sess_add snmp_sess_add_ex探测引擎IDsnmpv3_engineID_probe探测引擎IDsptr-probe_engineid(snmpusm: usm_discover_engineid)添加userlistsptr-post_probe_engineid(usm_create_user_from_session_hook)2.3.2 探测流程usm_discover_engineid生成空pdu请求usm_build_probe_pdu同步探测snmp_sess_synch_response发送请求snmp_sess_send接收请求snmp_sess_read2 _sess_read处理数据包_sess_process_packet将数据包解析为协议数据单元pdu_sess_process_packet_parse_pdu_snmp_parsesnmpv3_parse解析msgGlobalData解码验证传入数据回调函数decodeusm_process_in_msg解析安全参数usm_parse_security_parameters如果需要认证或加密usm_check_and_update_timeliness重点如果不需要认证加密set_enginetime重点处理协议数据单元调用回调函数_sess_process_packet_handle_pdu会调用我们注册的回调函数session.callbback后序步骤省略本文重点不在介绍此处...三、设置etimelist过程梳理介绍前面两处重点/* * Perform the timeliness/time manager functions. */ if (secLevel SNMP_SEC_LEVEL_AUTHNOPRIV || secLevel SNMP_SEC_LEVEL_AUTHPRIV) { if (usm_check_and_update_timeliness(secEngineID, *secEngineIDLen, boots_uint, time_uint, error) -1) { goto err; } } #ifdef LCD_TIME_SYNC_OPT /* * Cache the unauthenticated time to use in case we dont have * anything better - this guess will be no worse than (0,0) * that we normally use. */ else { set_enginetime(secEngineID, *secEngineIDLen, boots_uint, time_uint, FALSE); } #endif /* LCD_TIME_SYNC_OPT */当我们发送空pdu进行触发设备响应其引擎id、启动次数、窗口时间时以及探测过程中设置的认证和加密不对的时候交换机返回的响应包里面secLeavel都是1详情见下图也就是noauthnopriv所以一定进入的是set_enginetime传入的最后一个参数为FALSE意味着此窗口时间不是权威的。3.1 情形一引擎冲突设备全部认证加密密码不对当所有设备都没有认证通过时意味着这些设备不是权威的参见之前set_enginetime函数我们可以知道if (authenticated || !e-authenticatedFlag) 判断条件一定为true结果就是此冲突engineid对应的engineboot和enginetime一直在被覆盖而且下一台设备请求的时候会使用上次覆盖的boot和time值。3.2 情形二引擎冲突设备部分认证加密密码是对的当部分设备已经认证信息是正确的时也就意味着此冲突engineid的authenticatedFlag为1那么认证不通过的设备无论响应什么值都不会再被认可而认证通过的设备则通过usm_check_and_update_timeliness函数调用的set_enginetime()传入的事TRUE所以这些设备之间会相互覆盖engineboot和enginetime值。PS对于认证通过的设备究竟哪一台会胜出我们在情况三中统一说明3.3 情形三引擎冲突设备认证加密密码全部是对的当全部引擎ID冲突设备的认证和加密信息都正确时那么这些设备之间会相互覆盖engineboot和enginetime值但究竟谁会胜出呢如果一直相互覆盖那所有设备联动状态都是异常的了而实际联动过程中会发现总会有一台是联动正常的。下面我们看net-snmp代码int usm_create_user_from_session(netsnmp_session * session) { struct usmUser *user; int user_just_created 0; char *cp; /* * - dont create-another/copy-into user for this session by default * - bail now (no error) if we dont have an engineID */ if (SNMP_FLAGS_USER_CREATED (session-flags SNMP_FLAGS_USER_CREATED) || session-securityModel ! SNMP_SEC_MODEL_USM || session-version ! SNMP_VERSION_3 || session-securityNameLen 0 || session-securityEngineIDLen 0) return SNMPERR_SUCCESS; DEBUGMSGTL((usm, no flag defined... continuing\n)); session-flags | SNMP_FLAGS_USER_CREATED; /* * now that we have the engineID, create an entry in the USM list * for this user using the information in the session */ user usm_get_user_from_list(session-securityEngineID, session-securityEngineIDLen, session-securityName, usm_get_userList(), 0); if (NULL ! user) { DEBUGMSGTL((usm, user exists x%p\n, user)); } else { if (usm_build_user(user, session) ! SNMPERR_SUCCESS) return SNMPERR_GENERR; user_just_created 1; } /* * copy the auth protocol */ if (user-authProtocol NULL session-securityAuthProto ! NULL) { SNMP_FREE(user-authProtocol); user-authProtocol snmp_duplicate_objid(session-securityAuthProto, session-securityAuthProtoLen); if (user-authProtocol NULL) { usm_free_user(user); return SNMPERR_GENERR; } user-authProtocolLen session-securityAuthProtoLen; } /* * copy the priv protocol */ if (user-privProtocol NULL session-securityPrivProto ! NULL) { SNMP_FREE(user-privProtocol); user-privProtocol snmp_duplicate_objid(session-securityPrivProto, session-securityPrivProtoLen); if (user-privProtocol NULL) { usm_free_user(user); return SNMPERR_GENERR; } user-privProtocolLen session-securityPrivProtoLen; } /* * copy in the authentication Key. If not localized, localize it */ if (user-authKey NULL) { if (session-securityAuthLocalKey ! NULL session-securityAuthLocalKeyLen ! 0) { /* already localized key passed in. use it */ SNMP_FREE(user-authKey); user-authKey netsnmp_memdup(session-securityAuthLocalKey, session-securityAuthLocalKeyLen); if (!user-authKey) { usm_free_user(user); return SNMPERR_GENERR; } user-authKeyLen session-securityAuthLocalKeyLen; } else if (session-securityAuthKeyLen ! 0) { SNMP_FREE(user-authKey); user-authKey (u_char *) calloc(1, USM_LENGTH_KU_HASHBLOCK); user-authKeyLen USM_LENGTH_KU_HASHBLOCK; if ((user-authKey NULL) || generate_kul(user-authProtocol, user-authProtocolLen, user-engineID, user-engineIDLen, session-securityAuthKey, session-securityAuthKeyLen, user-authKey, user-authKeyLen) ! SNMPERR_SUCCESS) { usm_free_user(user); return SNMPERR_GENERR; } } else if ((cp netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_AUTHLOCALIZEDKEY))) { size_t buflen USM_AUTH_KU_LEN; SNMP_FREE(user-authKey); user-authKey (u_char *)malloc(buflen); /* max length needed */ user-authKeyLen 0; /* it will be a hex string */ if ((NULL user-authKey) || !snmp_hex_to_binary(user-authKey, buflen, user-authKeyLen, 0, cp)) { usm_free_user(user); return SNMPERR_GENERR; } } } /* * copy in the privacy Key. If not localized, localize it */ if (user-privKey NULL) { /** save buffer size in case we need to extend key */ int keyBufSize USM_PRIV_KU_LEN; DEBUGMSGTL((usm, copying privKey\n)); if (session-securityPrivLocalKey ! NULL session-securityPrivLocalKeyLen ! 0) { /* already localized key passed in. use it */ SNMP_FREE(user-privKey); user-privKey netsnmp_memdup(session-securityPrivLocalKey, session-securityPrivLocalKeyLen); if (!user-privKey) { usm_free_user(user); return SNMPERR_GENERR; } keyBufSize user-privKeyLen session-securityPrivLocalKeyLen; } else if (session-securityPrivKeyLen ! 0) { SNMP_FREE(user-privKey); user-privKey (u_char *) calloc(1, keyBufSize); user-privKeyLen keyBufSize; if ((user-privKey NULL) || generate_kul(user-authProtocol, user-authProtocolLen, user-engineID, user-engineIDLen, session-securityPrivKey, session-securityPrivKeyLen, user-privKey, user-privKeyLen) ! SNMPERR_SUCCESS) { usm_free_user(user); return SNMPERR_GENERR; } } else if ((cp netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_PRIVLOCALIZEDKEY))) { size_t buflen keyBufSize; user-privKey (u_char *)malloc(buflen); /* max length needed */ user-privKeyLen 0; /* it will be a hex string */ if ((NULL user-privKey) || !snmp_hex_to_binary(user-privKey, buflen, user-privKeyLen, 0, cp)) { usm_free_user(user); return SNMPERR_GENERR; } } if (usm_extend_user_kul(user, keyBufSize) ! SNMPERR_SUCCESS) { usm_free_user(user); return SNMPERR_GENERR; } } if (user_just_created) { /* * add the user into the database */ user-userStatus RS_ACTIVE; user-userStorageType ST_READONLY; usm_add_user(user); } DEBUGMSGTL((9:usm, user created\n)); return SNMPERR_SUCCESS; }当探测引擎ID完成之后post_probe_engineid函数会调用usm_create_user_from_session_hook基于当前session配置的认证账号密码生成authkey和privkey并且create user加入到全局的puserlist中。而我们之前提过解包的时候decode回调函数usm_process_in_msg这个函数内部在set_enginetime之前有两个步骤至关重要1基于引擎ID获取全局userlist中的用户信息usm_get_user_from_list2调用sc_check_keyed_hash函数基于获取的puser进行校验以下为解释至此我相信大家也就看出问题了吧认证和加密信息正确的情况下谁第一个探测完并且将信息加入到全局的userlist谁就胜出成为那台联动成功的设备了。此处虽然介绍的是收包解析但发包的时候也是如此也是使用userlist中的authkey和privkey来加密数据而剩余的设备要么收包后校验失败要么因为使用错误的authkey和privkey去向设备请求信息导致认证失败。3.4 情形四引擎冲突设备认证加密密码全部是对的密码一致当认证加密密码一致的时候就不会存在情况三中的解包或者发包校验问题但会检查窗口时间。当全部联动成功认证加密一致的时候engineboot和enginetime会覆盖但不是相互覆盖而是采用递增覆盖的方式最大的会胜出小的会报错SNMPERR_USM_NOTINTIMEWINDOW。详情看usm_check_and_update_timeliness里面的代码逻辑u_int theirBoots, theirTime, theirLastTime; u_int time_difference; if (get_enginetime_ex(secEngineID, secEngineIDLen, theirBoots, theirTime, theirLastTime, TRUE) ! SNMPERR_SUCCESS) { DEBUGMSGTL((usm, %s\n, Failed to get remote engines times.)); *error SNMPERR_USM_GENERICERROR; return -1; } time_difference theirTime time_uint ? theirTime - time_uint : time_uint - theirTime; /* * XXX Contrary to the pseudocode: * See if boots is invalid first. */ if (theirBoots ENGINEBOOT_MAX || theirBoots boots_uint) { DEBUGMSGTL((usm, %s\n, Remote boot count invalid.)); *error SNMPERR_USM_NOTINTIMEWINDOW; return -1; } /* * Boots is ok, see if the boots is the same but the time * is old. */ if (theirBoots boots_uint time_uint theirLastTime) { if (time_difference USM_TIME_WINDOW) { DEBUGMSGTL((usm, %s\n, Message too old.)); *error SNMPERR_USM_NOTINTIMEWINDOW; return -1; } else { /* Old, but acceptable */ *error SNMPERR_SUCCESS; return 0; } } /* * Message is ok, either boots has been advanced, or * time is greater than before with the same boots. */ if (set_enginetime(secEngineID, secEngineIDLen, boots_uint, time_uint, TRUE) ! SNMPERR_SUCCESS) { DEBUGMSGTL((usm, %s\n, Failed updating remote boot/time.)); *error SNMPERR_USM_GENERICERROR; return -1; } *error SNMPERR_SUCCESS; return 0; /* Fresh message and time updated */ } /* endif -- local or remote time reference. */以上就是我的理解如有不正确欢迎大家指正功能进步谢谢