在 SNMP 中,要确保 [nextCmd] 函数只遍历当前的 MIB 子树而不进入下一级子树,可以通过设置 [lexicographicMode]参数来实现。将这个参数已经被设置为 [False],这是关键设置。
lexicographicMode=False
当 [lexicographicMode]设置为 false
时,[nextCmd]函数的行为会受到限制,使其只在指定的 OID 子树内进行遍历。这是因为 SNMP 的 WALK 操作本质上是通过连续发送 SNMP GETNEXT 请求来实现的,每个请求基于上一个请求的结果来获取下一个 OID。
工作原理
-
OID 排序:
- OID(对象标识符)在 MIB(管理信息库)中是按照字典顺序排列的。每个 OID 都可以看作是一个节点,可能有多个子节点形成一个子树。
-
GETNEXT 请求:
- SNMP GETNEXT 请求用于查询字典顺序中当前 OID 的下一个 OID。如果当前 OID 是某个子树的最后一个节点,那么正常情况下(
lexicographicMode=True
),下一个 GETNEXT 请求将返回紧接着的下一个子树的第一个 OID。
- SNMP GETNEXT 请求用于查询字典顺序中当前 OID 的下一个 OID。如果当前 OID 是某个子树的最后一个节点,那么正常情况下(
-
限制遍历范围:
- 当 [lexicographicMode]设置为
false
,[nextCmd] 会在到达当前子树的逻辑末端时停止发送 GETNEXT 请求。这是通过检查每个新获取的 OID 是否仍然属于初始指定的 OID 子树来实现的。如果新的 OID 不再属于起始 OID 的子树,[nextCmd] 将不再继续请求更多的 OID,从而结束遍历。
- 当 [lexicographicMode]设置为
实际效果
这意味着,如果开始于一个特定的 OID,比如 .1.3.6.1.2.1.1
,并且设置 [lexicographicMode=False],[nextCmd] 将只遍历 .1.3.6.1.2.1.1
下的所有子节点,直到这个分支的末端。一旦到达这个分支的逻辑末端,即使 MIB 中还有更多的 OID,[nextCmd] 也不会继续前进到其他分支,如 .1.3.6.1.2.1.2
。
这种设置对于限制 SNMP 查询的范围非常有用,特别是在大型网络中,可以显著减少不必要的网络流量和处理时间,同时确保查询结果的相关性和精确性。
在 SNMP 中,当使用 [nextCmd] 函数并设置 [lexicographicMode=False]时,内部逻辑会自动处理判断当前 OID 是否为初始子树的逻辑末端。这个判断基于 OID 的结构和排序规则。下面是如何进行这种判断的详细解释:
OID 结构和比较
OIDs (对象标识符) 是按照树状结构组织的,每个 OID 可以视为一个路径,从根开始,通过一系列的数字(节点)来唯一标识信息的位置。例如,OID .1.3.6.1.2.1.1
可以被视为从根节点开始的路径。
判断逻辑
当 [nextCmd] 发送 SNMP GETNEXT 请求并接收到响应时,它会获取当前 OID 的“下一个” OID。这里的“下一个”是基于字典顺序的。[nextCmd] 会检查这个新的 OID 是否仍然是初始 OID 的子节点。这是通过比较 OID 前缀来实现的:
- 前缀匹配:如果新的 OID 在字典顺序中仍然以初始 OID 作为前缀,那么它被认为是初始子树的一部分。例如,如果初始 OID 是
.1.3.6.1.2.1.1
,那么.1.3.6.1.2.1.1.1
和.1.3.6.1.2.1.1.2
都是它的子节点。 - 前缀不匹配:如果新的 OID 不以初始 OID 作为前缀,这意味着 [nextCmd]已经越过了初始子树的边界。例如,从
.1.3.6.1.2.1.1
到.1.3.6.1.2.1.2
就是越过了边界。
自动停止
当 [lexicographicMode=False],如果 [nextCmd] 检测到新的 OID 越过了初始子树的边界,它将自动停止发送进一步的 GETNEXT 请求。这个停止是内部实现的,不需要用户额外编码来控制。
这种机制确保了 SNMP WALK 操作的效率和准确性,只获取相关的 MIB 数据,避免了不必要的网络负载和处理开销。
解释 [lexicographicMode] 参数:
-
lexicographicMode=True:
- 当设置为 [True] 时,[nextCmd]将按字典顺序遍历整个 MIB 树,从指定的起始 OID 开始,直到 MIB 的末尾。这意味着它会超出原始指定的子树范围,进入其他子树进行搜索。
-
lexicographicMode=False:
- 当设置为 [False]时,[nextCmd]会在到达当前子树的末端时停止。这意味着它只会遍历从指定 OID 开始的那部分子树,一旦到达这个子树的逻辑末端,即使 MIB 中还有更多的 OID,它也不会继续前进。
实现细节:
当 [nextCmd] 遍历到一个 OID,它会检查这个 OID 是否仍然属于初始指定的子树。如果当前的 OID 超出了起始 OID 的范围(即不再是起始 OID 的子节点),并且 [lexicographicMode] 设置为 [False],则 [nextCmd] 会停止遍历。
这种方式非常适合于需要精确控制 SNMP 查询范围的情况,确保查询只限于特定的子树,从而提高效率并减少不必要的网络流量和处理时间。
def walk_subtree(ip, oid):
result_text = ""
for (error_indication, error_status, error_index, var_binds) in nextCmd(
SnmpEngine(),
CommunityData('public', mpModel=0),
UdpTransportTarget((ip, 161)),
ContextData(),
ObjectType(ObjectIdentity(oid)),
lexicographicMode=False
):
if error_indication:
return f"SNMP WALK request failed: {error_indication}"
elif error_status:
return f"SNMP WALK error at {error_index}: {error_status.prettyPrint()}"
else:
for var_bind in var_binds:
result_text += f"{var_bind[0].prettyPrint()} = {var_bind[1].prettyPrint()}\n"
return result_text.strip() if result_text else "No entries found under this OID."
def snmp_walk(ip, community, oid):
iterator = nextCmd(
SnmpEngine(),
CommunityData(community, mpModel=0),
UdpTransportTarget((ip, 161)),
ContextData(),
ObjectType(ObjectIdentity(oid))
)
while True:
try:
errorIndication, errorStatus, errorIndex, varBinds = next(iterator)
if errorIndication:
print(errorIndication)
break
elif errorStatus:
print('%s at %s' % (
errorStatus.prettyPrint(),
errorIndex and varBinds[int(errorIndex) - 1][0] or '?'
))
else:
for varBind in varBinds:
print(' = '.join([x.prettyPrint() for x in varBind]))
except StopIteration:
break
except Exception as ex: # 捕获所有其他异常
print(f"An error occurred: {ex}")
[snmp_walk]和的 [walk_subtree] 函数主要有以下几个区别:
-
异常处理:
- 在 [snmp_walk] 中,除了处理 SNMP 的标准错误([errorIndication]和 [errorStatus]),还通过一个通用的
except Exception as ex
捕获了所有其他可能的异常,并打印出错误信息。这增加了代码的健壮性,能够处理更多意外情况。 walk_subtree
函数则没有显示地处理除 SNMP 错误以外的其他异常。
- 在 [snmp_walk] 中,除了处理 SNMP 的标准错误([errorIndication]和 [errorStatus]),还通过一个通用的
-
返回值:
walk_subtree
函数构建并返回一个结果字符串,包含所有遍历到的 OID 及其值。这使得函数的输出可以直接用于进一步处理或显示。snmp_walk
函数则直接在控制台打印每个变量绑定的结果,不返回任何值。这适用于直接观察结果,但不便于程序内部进一步处理数据。
-
参数设置:
walk_subtree
函数中明确设置了lexicographicMode=False
,确保只遍历指定的子树。这是一个重要的设置,用于限制 SNMP WALK 的范围。snmp_walk
函数没有设置lexicographicMode
,默认情况下,它可能会遍历整个 MIB,超出初始指定的子树。
-
社区字符串的使用:
walk_subtree
函数中硬编码了社区字符串为 “public”,而snmp_walk
函数则将社区字符串作为参数传入,提供了更高的灵活性。
这些差异使得两个函数在实际应用中的适用场景有所不同。snmp_walk
更适合于调试和日志记录,而 walk_subtree
更适合于需要处理或分析返回数据的应用场景。
在之前的讨论中,我们提到了 [lexicographicMode]参数,这是 [nextCmd]函数的一个重要参数,用于控制 SNMP WALK 的行为。然而,在这里的 [snmp_walk]函数中,这个参数没有被显式设置,因此默认情况下,[lexicographicMode] 是 [True]。这意味着 SNMP WALK 操作可能会遍历整个 MIB,而不仅仅是指定的子树。
如何设置以确保只在当前子树进行查询
要确保 [nextCmd]函数只在当前子树进行查询,需要在调用 [nextCmd] 时设置 [lexicographicMode=False]。这样设置后,遍历将在到达子树的末端时停止,而不会继续到其他的 MIB 分支。
修改代码示例
可以修改 [snmp_walk]函数,添加 [lexicographicMode=False] 参数,如下所示:
def snmp_walk(ip, community, oid):
iterator = nextCmd(
SnmpEngine(),
CommunityData(community, mpModel=0),
UdpTransportTarget((ip, 161)),
ContextData(),
ObjectType(ObjectIdentity(oid)),
lexicographicMode=False # 确保只遍历当前子树
)
while True:
try:
errorIndication, errorStatus, errorIndex, varBinds = next(iterator)
if errorIndication:
print(errorIndication)
break
elif errorStatus:
print('%s at %s' % (
errorStatus.prettyPrint(),
errorIndex and varBinds[int(errorIndex) - 1][0] or '?'
))
else:
for varBind in varBinds:
print(' = '.join([x.prettyPrint() for x in varBind]))
except StopIteration:
break
except Exception as ex: # 捕获所有其他异常
print(f"An error occurred: {ex}")
通过这种方式,可以确保 snmp_walk
函数只在指定的 OID 子树内进行 SNMP WALK 操作,避免不必要的网络流量和处理时间。