mirror of
https://git.proxmox.com/git/mirror_frr
synced 2025-07-14 03:50:57 +00:00

Noticed that we were not really attempting to even test large swaths of our snmp infrastructure. Let's load up some very simple configs for those daemons that FRR supports and ensure that SNMP is working to some extent. Signed-off-by: Donald Sharp <sharpd@nvidia.com>
291 lines
9.4 KiB
Python
291 lines
9.4 KiB
Python
# SPDX-License-Identifier: ISC
|
|
#
|
|
# topogen.py
|
|
# Library of helper functions for NetDEF Topology Tests
|
|
#
|
|
# Copyright (c) 2020 by Volta Networks
|
|
#
|
|
#
|
|
|
|
"""
|
|
SNMP library to test snmp walks and gets
|
|
|
|
Basic usage instructions:
|
|
|
|
* define an SnmpTester class giving a router, address, community and version
|
|
* use test_oid or test_walk to check values in MIBS
|
|
* see tests/topotest/simple-snmp-test/test_simple_snmp.py for example
|
|
"""
|
|
|
|
from lib.topolog import logger
|
|
import re
|
|
|
|
|
|
class SnmpTester(object):
|
|
"A helper class for testing SNMP"
|
|
|
|
def __init__(self, router, iface, community, version, options=""):
|
|
self.community = community
|
|
self.version = version
|
|
self.router = router
|
|
self.iface = iface
|
|
self.options = options
|
|
logger.info(
|
|
"created SNMP tester: SNMPv{0} community:{1}".format(
|
|
self.version, self.community
|
|
)
|
|
)
|
|
|
|
def _snmp_config(self):
|
|
"""
|
|
Helper function to build a string with SNMP
|
|
configuration for commands.
|
|
"""
|
|
return "-v {0} -c {1} {2} {3}".format(
|
|
self.version, self.community, self.options, self.iface
|
|
)
|
|
|
|
@staticmethod
|
|
def _get_snmp_value(snmp_output):
|
|
tokens = snmp_output.strip().split()
|
|
|
|
num_value_tokens = len(tokens) - 3
|
|
|
|
# this copes with the emptys string return
|
|
if num_value_tokens == 0:
|
|
return tokens[2]
|
|
|
|
if num_value_tokens > 1:
|
|
output = ""
|
|
index = 3
|
|
while index < len(tokens) - 1:
|
|
output += "{} ".format(tokens[index])
|
|
index += 1
|
|
output += "{}".format(tokens[index])
|
|
return output
|
|
# third token is the value of the object
|
|
return tokens[3]
|
|
|
|
@staticmethod
|
|
def _get_snmp_oid(snmp_output):
|
|
tokens = snmp_output.strip().split()
|
|
|
|
# third token onwards is the value of the object
|
|
return tokens[0].split(".", 1)[1]
|
|
|
|
def _parse_multiline(self, snmp_output):
|
|
results = snmp_output.strip().split("\n")
|
|
|
|
out_dict = {}
|
|
out_list = []
|
|
for response in results:
|
|
out_dict[self._get_snmp_oid(response)] = self._get_snmp_value(response)
|
|
out_list.append(self._get_snmp_value(response))
|
|
|
|
return out_dict, out_list
|
|
|
|
def get(self, oid):
|
|
cmd = "snmpget {0} {1} 2>&1 | grep -v SNMPv2-PDU".format(
|
|
self._snmp_config(), oid
|
|
)
|
|
result = self.router.cmd(cmd)
|
|
if "not found" in result:
|
|
return None
|
|
return self._get_snmp_value(result)
|
|
|
|
def get_next(self, oid):
|
|
cmd = "snmpgetnext {0} {1} 2>&1 | grep -v SNMPv2-PDU".format(
|
|
self._snmp_config(), oid
|
|
)
|
|
|
|
result = self.router.cmd(cmd)
|
|
print("get_next: {}".format(result))
|
|
if "not found" in result:
|
|
return None
|
|
return self._get_snmp_value(result)
|
|
|
|
def walk(self, oid, raw=False):
|
|
cmd = "snmpwalk {0} {1} 2>&1 | grep -v SNMPv2-PDU".format(
|
|
self._snmp_config(), oid
|
|
)
|
|
|
|
result = self.router.cmd(cmd)
|
|
|
|
if raw:
|
|
return result
|
|
|
|
return self._parse_multiline(result)
|
|
|
|
def parse_notif_ipv4(self, notif):
|
|
# normalise values
|
|
notif = re.sub(":", "", notif)
|
|
notif = re.sub('"([0-9]{2}) ([0-9]{2}) "', r"\1\2", notif)
|
|
notif = re.sub('"([0-9]{2}) "', r"\1", notif)
|
|
elems = re.findall(r"([0-9,\.]+) = ([0-9,\.]+)", notif)
|
|
|
|
# remove common part
|
|
elems = elems[1:]
|
|
return elems
|
|
|
|
def is_notif_bgp4_valid(self, output_list, address):
|
|
oid_notif_type = ".1.3.6.1.6.3.1.1.4.1.0"
|
|
peer_notif_established = ".1.3.6.1.2.1.15.0.1"
|
|
peer_notif_backward = ".1.3.6.1.2.1.15.0.2"
|
|
oid_peer_last_error = ".1.3.6.1.2.1.15.3.1.14"
|
|
oid_peer_remote_addr = ".1.3.6.1.2.1.15.3.1.7"
|
|
oid_peer_state = ".1.3.6.1.2.1.15.3.1.2"
|
|
|
|
nb_notif = len(output_list)
|
|
for nb in range(0, nb_notif - 1):
|
|
# identify type of notification
|
|
# established or BackwardTransition
|
|
|
|
if output_list[nb][0][0] != "{}".format(oid_notif_type):
|
|
return False
|
|
|
|
if output_list[nb][0][1] == "{}".format(peer_notif_established):
|
|
logger.info("Established notification")
|
|
elif output_list[nb][0][1] == "{}".format(peer_notif_backward):
|
|
logger.info("Backward transition notification")
|
|
else:
|
|
return False
|
|
|
|
# same behavior for 2 notification type in bgp4
|
|
if output_list[nb][1][0] != "{}.{}".format(oid_peer_remote_addr, address):
|
|
return False
|
|
|
|
if output_list[nb][2][0] != "{}.{}".format(oid_peer_last_error, address):
|
|
return False
|
|
if output_list[nb][3][0] != "{}.{}".format(oid_peer_state, address):
|
|
return False
|
|
|
|
return True
|
|
|
|
def is_notif_bgp4v2_valid(self, output_list, address, type_requested):
|
|
oid_notif_type = ".1.3.6.1.6.3.1.1.4.1.0"
|
|
peer_notif_established = ".1.3.6.1.3.5.1.0.1"
|
|
peer_notif_backward = ".1.3.6.1.3.5.1.0.2"
|
|
oid_peer_state = ".1.3.6.1.3.5.1.1.2.1.13"
|
|
oid_peer_local_port = ".1.3.6.1.3.5.1.1.2.1.6"
|
|
oid_peer_remote_port = ".1.3.6.1.3.5.1.1.2.1.9"
|
|
oid_peer_err_code_recv = ".1.3.6.1.3.5.1.1.3.1.1"
|
|
oid_peer_err_sub_code_recv = ".1.3.6.1.3.5.1.1.3.1.2"
|
|
oid_peer_err_recv_text = ".1.3.6.1.3.5.1.1.3.1.4"
|
|
|
|
nb_notif = len(output_list)
|
|
for nb in range(nb_notif):
|
|
if output_list[nb][0][0] != "{}".format(oid_notif_type):
|
|
return False
|
|
|
|
if output_list[nb][0][1] == "{}".format(peer_notif_established):
|
|
logger.info("Established notification")
|
|
notif_type = "Estab"
|
|
|
|
elif output_list[nb][0][1] == "{}".format(peer_notif_backward):
|
|
logger.info("Backward transition notification")
|
|
notif_type = "Backward"
|
|
else:
|
|
return False
|
|
|
|
if notif_type != type_requested:
|
|
continue
|
|
|
|
if output_list[nb][1][0] != "{}.1.{}".format(oid_peer_state, address):
|
|
continue
|
|
|
|
if output_list[nb][2][0] != "{}.1.{}".format(oid_peer_local_port, address):
|
|
return False
|
|
|
|
if output_list[nb][3][0] != "{}.1.{}".format(oid_peer_remote_port, address):
|
|
return False
|
|
|
|
if notif_type == "Estab":
|
|
return True
|
|
|
|
if output_list[nb][4][0] != "{}.1.{}".format(
|
|
oid_peer_err_code_recv, address
|
|
):
|
|
return False
|
|
|
|
if output_list[nb][5][0] != "{}.1.{}".format(
|
|
oid_peer_err_sub_code_recv, address
|
|
):
|
|
return False
|
|
|
|
if output_list[nb][6][0] != "{}.1.{}".format(
|
|
oid_peer_err_recv_text, address
|
|
):
|
|
return False
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
def get_notif_bgp4(self, output_file):
|
|
notifs = []
|
|
notif_list = []
|
|
whitecleanfile = re.sub("\t", " ", output_file)
|
|
results = whitecleanfile.strip().split("\n")
|
|
|
|
# don't consider additional SNMP or application messages
|
|
for result in results:
|
|
if re.search(r"(\.([0-9]+))+\s", result):
|
|
notifs.append(result)
|
|
|
|
oid_v4 = r"1\.3\.6\.1\.2\.1\.15"
|
|
for one_notif in notifs:
|
|
is_ipv4_notif = re.search(oid_v4, one_notif)
|
|
if is_ipv4_notif != None:
|
|
formated_notif = self.parse_notif_ipv4(one_notif)
|
|
notif_list.append(formated_notif)
|
|
|
|
return notif_list
|
|
|
|
def get_notif_bgp4v2(self, output_file):
|
|
notifs = []
|
|
notif_list = []
|
|
whitecleanfile = re.sub("\t", " ", output_file)
|
|
results = whitecleanfile.strip().split("\n")
|
|
|
|
# don't consider additional SNMP or application messages
|
|
for result in results:
|
|
if re.search(r"(\.([0-9]+))+\s", result):
|
|
notifs.append(result)
|
|
|
|
oid_v6 = r"1\.3\.6\.1\.3\.5\.1"
|
|
for one_notif in notifs:
|
|
is_ipv6_notif = re.search(oid_v6, one_notif)
|
|
if is_ipv6_notif != None:
|
|
formated_notif = self.parse_notif_ipv4(one_notif)
|
|
notif_list.append(formated_notif)
|
|
|
|
return notif_list
|
|
|
|
def test_oid(self, oid, value):
|
|
print("oid: {}".format(self.get_next(oid)))
|
|
return self.get_next(oid) == value
|
|
|
|
def test_oid_walk(self, oid, values, oids=None):
|
|
results_dict, results_list = self.walk(oid)
|
|
print("test_oid_walk: {} {}".format(oid, results_dict))
|
|
if oids is not None:
|
|
index = 0
|
|
for oid in oids:
|
|
# avoid key error for missing keys
|
|
if not oid in results_dict.keys():
|
|
print("FAIL: missing oid key {}".format(oid))
|
|
return False
|
|
if results_dict[oid] != values[index]:
|
|
print(
|
|
"FAIL{} {} |{}| == |{}|".format(
|
|
oid, index, results_dict[oid], values[index]
|
|
)
|
|
)
|
|
return False
|
|
index += 1
|
|
return True
|
|
|
|
# Return true if 'values' is a subset of 'results_list'
|
|
print("test {} == {}".format(results_list[: len(values)], values))
|
|
return results_list[: len(values)] == values
|