Skip to content

Option to exclude interfaces #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 112 additions & 3 deletions lib/check/interface.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from asyncsnmplib.mib.mib_index import MIB_INDEX
from collections import Counter
from libprobe.asset import Asset
from libprobe.exceptions import CheckException
from ..snmpquery import snmpquery

QUERIES = (
Expand Down Expand Up @@ -54,28 +55,136 @@
'HCOutBroadcastPkts',
)

ExcludedIfTypes = (
'ieee80211',
'l3ipvlan',
'l2vlan',
)

ExcludedIfDescStartsWith = (
'veth',
'nu',
'vnet',
'virbr',
)

ExcludedIfDescContains = (
'vif',
'stackport',
'internal-data',
'cplane'
)

# Address and prefixes matching these prefixes will be absolutely filtered
ReservedAddresses = (
'00:00:01:00:00:01', # Problematic XEROX CORPORATION MACs
'00:00:01', # Cisco ASA virtual MACs
'01:00:01',
'00:01:00:00:00:01',
'00:08:e3', # Cisco unicast
'00:21:00:00:00:22',
'00:13:00:00:00:14',
'00:0f:b7:48:48',
'00:16:00:00:00:16',
'00:00:15:00:00:00',
'00:15:00:00:00:15',
'00:14:00:00:00:14',
'00:14:00:00:00:15',
'00:01:00:00:00:01',
'00:21:00:00:00:21',
'00:21:00:00:00:22',
'7a:77:00:00:00:0',
'00:00:03:00:00:00',
'00:00:05:00:00:00',
'00:08:e3:ff:fc:28', # Problematic Cisco MACs
'00:08:e3:ff:fd:90',
'02:00:4c:4f:4f:50',
'02:50:f2:00:00:01',
'00:25:b5:00:00:0f',
'00:25:b5:00:00:1f',
'00:18:18:16',
'12:00:00:00:00:00',
'54:10:ec', # Microchip
'00:90:fa', # Emulex
'cc:4e:24', # PCS
'00:90:8f', # Audio codes
'38:90:a5:be', # Threat Defense
'b4:0c:25:e', # Palo Alto Firewall HA
'00:1b:17:00',
'ba:db:ad', # Palo Alto VMware interface
'00:a0:c9:00:00:00', # Firepower
'00:13:00:00:00:13', # Problematic LLDP chassis ID
'00:00:00', # ARP
'01:00:00:00:00',
'01:00:5e', # Used for IPV4 Multicast and MLPS Multicast
'33:33', # Reserved for IPV6 Multicast
'02:00:4c:4f:4f:50', # Microsoft Loopback adapter`
'20:41:53:59:4e:ff', # RAS
'00:22:bd:f8:19:ff', # Cisco ACI
'00:0b:ca:fe:00:00', # Avaya/Xen
'02:00:00', # Common default
'02:00:01',
'00:00:03', # Problematic Cisco ASA mac
'00:07:b4:00', # GLBP
'00:09:0f:09', # Fortinet HA
'00:10:db:ff:10', # Internal interfaces for Juniper
'02:42:ac:11', # Docker
'00:ff:c2:f3:cb:94', # Windows
'1e:8e:39:50:50:05:05:69:00', # VMware PVSCSI Controller
'c8:4f:86:fc:00', # Sophos virutal HA MAC
'02:0f:00:0b:98', # Sophos virutal HA MAC
)


async def check_interface(
asset: Asset,
asset_config: dict,
check_config: dict):

include_all = check_config.get('includeAllInterfaces', False)

state_data = await snmpquery(asset, asset_config, check_config, QUERIES)

counts = Counter()
itms = state_data.get('if', [])
items = []
if_x_entry = {i.pop('name'): i for i in state_data.pop('ifX', [])}
for item in itms:
key = item['name']
name = item['Descr']
if not include_all and item.get('Type') in ExcludedIfTypes:
continue

mac = item.get('PhysAddress')
if not include_all and isinstance(mac, str) and any(
mac.startswith(e) for e in ReservedAddresses):
continue

try:
name = item['Descr']
assert isinstance(name, str)
except (KeyError, AssertionError):
suggest = (
'; You might want to disable the option: '
'Include all interfaces'
) if include_all else ''
raise CheckException(
f'Missing ifDesc OID for creating an interface name{suggest}')

if not include_all and (
any(name.startswith(e) for e in ExcludedIfDescStartsWith) or
any(e in name for e in ExcludedIfDescContains)):
continue

idx = counts[name]
counts[name] += 1
item['name'] = f'{name}_{idx}' if idx else name

items.append(item)

try:
item.update(if_x_entry[key])
except KeyError:
continue
continue # no 64 bit counter, skip code below

for _64_bit_name in _64_BIT_COUNTERS:
if _64_bit_name in item:
Expand All @@ -90,5 +199,5 @@ async def check_interface(
item['Speed'] = item['HighSpeed'] * 1000000

return {
'interface': itms
'interface': items
}
8 changes: 8 additions & 0 deletions lib/check/ucd.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

QUERIES = (
MIB_INDEX['UCD-SNMP-MIB']['memory'],
MIB_INDEX['UCD-SNMP-MIB']['dskEntry'],
MIB_INDEX['UCD-DISKIO-MIB']['diskIOEntry'],
)

Expand Down Expand Up @@ -71,4 +72,11 @@ async def check_ucd(
counts[name] += 1
item['name'] = f'{name}_{idx}' if idx else name

counts = Counter()
for item in state_data.get('dsk', []):
name = item['Device']
idx = counts[name]
counts[name] += 1
item['name'] = f'{name}_{idx}' if idx else name

return state_data
65 changes: 42 additions & 23 deletions lib/snmpquery.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,30 @@
import logging
from asyncsnmplib.client import Snmp, SnmpV1, SnmpV3
from asyncsnmplib.exceptions import SnmpNoConnection, SnmpNoAuthParams
from asyncsnmplib.exceptions import SnmpException
from asyncsnmplib.exceptions import SnmpNoAuthParams
from asyncsnmplib.exceptions import SnmpNoConnection
from asyncsnmplib.mib.utils import on_result
from asyncsnmplib.v3.auth import AUTH_PROTO
from asyncsnmplib.v3.encr import PRIV_PROTO
from libprobe.asset import Asset
from libprobe.exceptions import CheckException, IgnoreResultException


class InvalidCredentialsException(SnmpException):
message = 'Invalid SNMP v3 credentials.'


class InvalidClientConfigException(SnmpException):
message = 'Invalid SNMP v3 client configuration.'


class InvalidSnmpVersionException(SnmpException):
message = 'Invalid SNMP version.'


class ParseResultException(SnmpException):
def __init__(self, message: str):
super().__init__(message)
self.message = message


def snmpv3_credentials(asset_config: dict):
Expand Down Expand Up @@ -66,16 +85,13 @@ async def snmpquery(
address = asset.name

version = asset_config.get('version', '2c')
community = asset_config.get('community', 'public')
if not isinstance(community, str):
try:
community = community['secret']
assert isinstance(community, str)
except KeyError:
logging.warning(f'missing snmp credentials {asset}')
raise IgnoreResultException

if version == '2c':
community = asset_config.get('community', 'public')
if isinstance(community, dict):
community = community.get('secret')
if not isinstance(community, str):
raise TypeError('SNMP community must be a string.')
cl = Snmp(
host=address,
community=community,
Expand All @@ -85,31 +101,36 @@ async def snmpquery(
cred = snmpv3_credentials(asset_config)
except Exception as e:
logging.warning(f'invalid snmpv3 credentials {asset}: {e}')
raise IgnoreResultException
raise InvalidCredentialsException
try:
cl = SnmpV3(
host=address,
**cred,
)
except Exception as e:
logging.warning(f'invalid snmpv3 client config {asset}: {e}')
raise IgnoreResultException
raise InvalidClientConfigException
elif version == '1':
community = asset_config.get('community', 'public')
if isinstance(community, dict):
community = community.get('secret')
if not isinstance(community, str):
raise TypeError('SNMP community must be a string.')
cl = SnmpV1(
host=address,
community=community,
)
else:
logging.warning(f'unsupported snmp version {asset}: {version}')
raise IgnoreResultException
raise InvalidSnmpVersionException

try:
await cl.connect()
except SnmpNoConnection as e:
raise CheckException(f'unable to connect')
except SnmpNoConnection:
raise
except SnmpNoAuthParams:
logging.warning(f'unable to connect: failed to set auth params')
raise IgnoreResultException()
logging.warning('unable to connect: failed to set auth params')
raise
else:
results = {}
try:
Expand All @@ -119,15 +140,13 @@ async def snmpquery(
name, result = on_result(oid, result)
except Exception as e:
msg = str(e) or type(e).__name__
raise CheckException(
f'parse result error: {msg}')
raise ParseResultException(
f'Failed to parse result. Exception: {msg}'
)
else:
results[name] = result
except CheckException:
except Exception:
raise
except Exception as e:
msg = str(e) or type(e).__name__
raise CheckException(msg)
else:
return results
finally:
Expand Down
2 changes: 1 addition & 1 deletion lib/version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Version string. Examples:
# '3.0.0'
# '3.0.0-alpha9'
__version__ = '3.0.5'
__version__ = '3.0.6'
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
asyncsnmplib==0.1.10
libprobe==0.2.33
asyncsnmplib==0.1.12
libprobe==0.2.36