diff --git a/README.md b/README.md index 1f83346fb03358c6f1805605b4955774868584f8..ee964880b04a003bbb5b61df7d3b77109ba29103 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[PACKAGE]: ../../raw/master/mkp/inv_ipv4_addresses-0.0.4-20240407.mkp "inv_ipv4_addresses-0.0.4-20240407.mkp" +[PACKAGE]: ../../raw/master/mkp/inv_ip_address-0.0.5-20241209.mkp "inv_ip_address-0.0.5-20241209.mkp" # Inventory of IP addresses The plugin adds the IP-addresses information to the inventory for devices monitored via SNMP. The plugin supports IPv4 and IPv6. diff --git a/mkp/inv_ip_address-0.0.5-20241209.mkp b/mkp/inv_ip_address-0.0.5-20241209.mkp new file mode 100644 index 0000000000000000000000000000000000000000..833326e6f28e095e4581d685cac51e08697256e2 Binary files /dev/null and b/mkp/inv_ip_address-0.0.5-20241209.mkp differ diff --git a/source/agent_based/inv_ipv4_addresses.py b/source/agent_based/inv_ipv4_addresses.py deleted file mode 100644 index 8a90ed5e0b45b34130f56d9ee9e7098a8306e081..0000000000000000000000000000000000000000 --- a/source/agent_based/inv_ipv4_addresses.py +++ /dev/null @@ -1,164 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# License: GNU General Public License v2 -# -# Author: thl-cmk[at]outlook[dot]com -# URL : https://thl-cmk.hopto.org -# Date : 2023-12-27 -# File : inv_ipv4_addresses.py -# -# inventory of IPv4 address information -# -# 2024-04-07: fixed missing/wrong netmask (ThX bitwiz@forum.checkmk.com) -# improved validation if SNMP input data -# drop this host ip address (0.0.0.0) - -from dataclasses import dataclass -from typing import List -from ipaddress import AddressValueError, IPv4Address, IPv4Network, NetmaskValueError - -from cmk.base.plugins.agent_based.agent_based_api.v1 import ( - OIDEnd, -) -from cmk.base.plugins.agent_based.agent_based_api.v1 import ( - HostLabel, - SNMPTree, - TableRow, - exists, - register, -) -from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( - HostLabelGenerator, - InventoryResult, - StringTable, -) - - -@dataclass(frozen=True) -class Ipv4Info: - address: str - broadcast: str - cidr: int - if_index: int | None - if_name: str - max_re_assemble: int | None - netmask: str - network: str - - -def parse_inv_ipv4_addresses(string_table: List[StringTable]) -> List[Ipv4Info] | None: - - try: - ipv4_info, if_info = string_table - except ValueError: - return - - try: - interface_by_index = {if_index: if_name for if_index, if_name in if_info} - except ValueError: - interface_by_index = {} - - ipv4_infos = [] - - for entry in ipv4_info: - try: - ipv4_address, if_index, ipv4_netmask, ipv4_bcast, max_re_assemble = entry - except ValueError: - continue - - try: - ipv4_address = IPv4Address(ipv4_address) - except AddressValueError: - continue - - if ipv4_address.is_loopback: # Drop localhost - continue - - if ipv4_address.exploded == '0.0.0.0': # drop this host address - continue - - try: - ipv4 = IPv4Network(address=f'{ipv4_address.exploded}/{ipv4_netmask}', strict=False) - except (AddressValueError, NetmaskValueError): - continue - - ipv4_infos.append( - Ipv4Info( - address=str(ipv4_address.exploded), - broadcast=str(ipv4.broadcast_address), - cidr=int(ipv4.prefixlen), - if_index=int(if_index) if if_index.isdigit() else None, - if_name=str(interface_by_index.get(if_index, if_index)), - max_re_assemble=int(max_re_assemble) if max_re_assemble.isdigit() else None, - netmask=str(ipv4.netmask), - network=str(ipv4.network_address), - ) - ) - - return ipv4_infos - - -def host_label_inv_ipv4_addresses(section: List[Ipv4Info]) -> HostLabelGenerator: - _non_host_ips = 0 - for ipv4 in section: - if ipv4.cidr != 32 and not ipv4.address.startswith('127.'): - _non_host_ips += 1 - if _non_host_ips > 1: - yield HostLabel(name="nvdct/routing_capable", value="yes") - - -def inventory_ipv4_addresses(section: List[Ipv4Info]) -> InventoryResult: - path = ['networking', 'addresses'] - - for ipv4_info in section: - key_columns = { - 'address': ipv4_info.address, - 'device': ipv4_info.if_name, - } - inventory_columns = { - 'broadcast': ipv4_info.broadcast, - 'cidr': ipv4_info.cidr, - # 'if_index': ipv4_info.if_index, - # 'max_re_assemble': ipv4_info.max_re_assemble, - 'netmask': ipv4_info.netmask, - 'network': ipv4_info.network, - 'type': 'ipv4', - } - yield TableRow( - path=path, - key_columns=key_columns, - inventory_columns=inventory_columns - ) - - -register.snmp_section( - name='inv_ipv4_addresses', - parse_function=parse_inv_ipv4_addresses, - host_label_function=host_label_inv_ipv4_addresses, - fetch=[ - SNMPTree( - base='.1.3.6.1.2.1.4.20.1', # IP-MIB::ipAddrEntry - oids=[ - '1', # ipAdEntAddr - '2', # ipAdEntIfIndex - '3', # ipAdEntNetMask - '4', # ipAdEntBcastAddr - '5', # ipAdEntReasmMaxSize - - ] - ), - SNMPTree( - base='.1.3.6.1.2.1.31.1.1.1', # - oids=[ - OIDEnd(), # ifIndex - '1', # ifName - ]), - ], - detect=exists('.1.3.6.1.2.1.4.20.1.1.*'), # -) - -register.inventory_plugin( - name='inv_ipv4_addresses', - inventory_function=inventory_ipv4_addresses, -) diff --git a/source/cmk_addons_plugins/inv_ip_address/agent_based/inv_ip_addresses.py b/source/cmk_addons_plugins/inv_ip_address/agent_based/inv_ip_addresses.py new file mode 100644 index 0000000000000000000000000000000000000000..92b6d60d699b316562661112bea3f4256b60ef86 --- /dev/null +++ b/source/cmk_addons_plugins/inv_ip_address/agent_based/inv_ip_addresses.py @@ -0,0 +1,275 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# License: GNU General Public License v2 +# +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org +# Date : 2023-12-27 +# File : inv_ip_addresses.py +# +# inventory of IPv4 address information +# +# 2024-04-07: fixed missing/wrong netmask (ThX bitwiz@forum.checkmk.com) +# improved validation of SNMP input data +# drop this host ip address (0.0.0.0) +# 2024-12-02: incompatible: changed host label to nvdct/l3v4_host:yes and nvdct/l3v4_routing:yes +# 2024-12-03: added IP-MIB::ipAddressTable for IPv6 support +# incompatible: renamed to inv_ip_address -> remove inv_ipv4_address.mkp before updating +# 2024-12-05 changed to use ip_interface +# 2024-12-06: incompatible: changed hostlabel to nvdct/l3v4_topology:host and nvdct/l3v4_topology:router +# 2024-12-09: rewritten for CMK checkAPI 2.0 + +from collections.abc import Mapping, MutableSequence, Sequence +from ipaddress import AddressValueError, NetmaskValueError, ip_interface +from re import match as re_match +from typing import List + +from cmk.agent_based.v2 import ( + HostLabel, + HostLabelGenerator, + InventoryPlugin, + InventoryResult, + OIDBytes, + OIDEnd, + SNMPSection, + SNMPTree, + StringByteTable, + TableRow, + exists, +) + + +__ip_info_34_ios = [ + [ + '1.4.10.10.10.230', # OID end -> type: ipv4, length: 4, ipv4 address + [], # ip address -> empty + '3', # interface index + '.1.3.6.1.2.1.4.32.1.5.3.1.4.10.10.10.228.30' # prefix -> last number (30) + ], + [ + '2.16.42.0.28.160.16.0.1.53.0.0.0.0.0.0.0.2', # OID end -> type: ipv6, length: 16, ipv6 address + [], + '3', + '.1.3.6.1.2.1.4.32.1.5.3.2.16.42.0.28.160.16.0.1.53.0.0.0.0.0.0.0.0.64' + ], + [ + '4.20.254.128.0.0.0.0.0.0.114.219.152.255.254.159.41.2.18.0.0.8', + # OID end -> type: ipv6z, length: 20, ipv6 address with interface identifier (18.0.0.8) + [], + '3', + '.0.0' + ], +] +__ip_info_34_ibm = [ + [ + '1.15.48.49.48.46.49.52.48.46.49.54.48.46.48.49.55', + # OID end -> type: ipv4, length: 15, ipv4 address ('010.140.160.017') + [], + '805306370', + '.0.0' + ], + [ + '2.39.48.48.48.48.58.48.48.48.48.58.48.48.48.48.58.48.48.48.48.58.48.48.48.48.58.48.48.48.48.58.48.48.48.48.58.48.48.48.49', + # OID end -> type: ipv6, length: 39, ipv6 address ('0000:0000:0000:0000:0000:0000:0000:0001') + [], + '805306371', + '.0.0' + ], +] +__ip_info_34_firepower = [ + [ + '1.10.1.1.2', # OID end -> type: ipv4, , ipv4 address ('10.1.1.2') + [10, 1, 1, 2], # ip address in dec bytes + '18', + '.1.3.6.1.2.1.4.32.1.5.18.1.10.1.1.0.24' + ], + [ + '2.253.0.0.0.0.0.0.1.0.0.0.0.0.0.0.1', + # OID end -> type: ipv6, ipv6 address ('253.0.0.0.0.0.0.1.0.0.0.0.0.0.0.1') + [253, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1], # ip address in dec bytes + '4', + '.1.3.6.1.2.1.4.32.1.5.4.2.253.0.0.0.0.0.0.1.0.0.0.0.0.0.0.0.64' + ], +] +__ip_info_34_fortinet = [ + [ + '1.10.118.132.1.76', # OID end -> type: ipv4, ipv4 address (10.118.132.1), interface index (76) + [], + '76', + '.0.0.0' # prefix -> missing + ], + [ + '2.10762.22982.8208.4113.0.0.0.282.40', + # OID end -> type: ipv6, ipv6 address (10762.22982.8208.4113.0.0.0.282), interface index (76) + [], + '40', + '.0.0.0' # prefix -> missing + ], +] + +Section = Sequence[Mapping[str, ip_interface]] | None + + +def parse_inv_ip_addresses(string_table: List[StringByteTable]) -> Section: + try: + ip_info_20, ip_info_34, if_info = string_table + except ValueError: + return + + try: + interface_by_index = {if_index: if_name for if_index, if_name in if_info} + except ValueError: + interface_by_index = {} + + ip_infos: MutableSequence[Mapping[str, ip_interface]] = [] + + for entry in ip_info_34: + try: + oid_end, dec_ip_address, if_index, ip_prefix = entry + except ValueError: + continue + + if (prefix := ip_prefix.split('.')[-1]) == '0': # drop entries without prefix (0) -> fortinet + continue + + if not (raw_ip := re_match(r'(\d+)\.(\d+)\.([\d|\.]+)', oid_end)): + continue + + raw_type, raw_length, raw_address = raw_ip.groups() + + if dec_ip_address: + raw_address = '.'.join(str(x) for x in dec_ip_address) + raw_length = str(len(dec_ip_address)) + + match raw_type: + case '1': # IPv4 address + if raw_length == '15': + raw_address = ''.join([chr(int(x)) for x in raw_address.split('.')]) + raw_address = '.'.join([str(int(x)) for x in raw_address.split('.')]) + case '2': # IPv6 address + match raw_length: + case '16': + raw_address = [f'{int(x):02x}' for x in raw_address.split('.')] + raw_address = ':'.join( + [''.join([raw_address[i], raw_address[i + 1]]) for i in range(0, len(raw_address), 2)] + ) + case '39': + raw_address = ''.join([chr(int(x)) for x in raw_address.split('.')]) + case _: + continue + + try: + interface = ip_interface(f'{raw_address}/{prefix}') + except (AddressValueError, NetmaskValueError): + continue + + if interface.ip.is_loopback: # Drop localhost + continue + + if interface.ip.exploded == '0.0.0.0': # drop this host address + continue + + ip_infos.append({(str(interface_by_index.get(if_index, if_index))): interface}) + + for entry in ip_info_20: + try: + raw_address, if_index, raw_netmask = entry + except ValueError: + continue + + try: + interface = ip_interface(f'{raw_address}/{raw_netmask}') + except (AddressValueError, NetmaskValueError): + continue + + if interface.ip.is_loopback: # Drop localhost + continue + + if interface.ip.exploded == '0.0.0.0': # drop this host address + continue + + ip_infos.append({str(interface_by_index.get(if_index, if_index)): interface}) + + return ip_infos + + +def host_label_inv_ip_addresses(section: Section) -> HostLabelGenerator: + """ + Host label function + Labels: + nvdct/l3v4_topology: + This label is set to "host" for all devices with one IPv4 address except form 127.0.0.0/8 and to + "router" for all devices with more than one IPv4 address except form 127.0.0.0/8 + """ + non_host_ips = 0 + for entry in section: + ip_data = entry.values() + if ip_data.version == 4 and not ip_data.ip.is_loopback: + non_host_ips += 1 + if non_host_ips == 1: + yield HostLabel(name="nvdct/l3v4_topology", value="host") + if non_host_ips > 1: + yield HostLabel(name="nvdct/l3v4_topology", value="router") + return + + +def inventory_ip_addresses(section: Section) -> InventoryResult: + address_type = { + 4: 'ipv4', + 6: 'ipv6', + } + for entry in section: + for if_name, ip_data in entry.items(): + yield TableRow( + path=['networking', 'addresses'], + key_columns={ + 'address': str(ip_data.ip.compressed), + 'device': if_name, + }, + inventory_columns={ + 'broadcast': str(ip_data.network.broadcast_address), + 'cidr': ip_data.network.prefixlen, + 'netmask': str(ip_data.network.netmask), + 'network': str(ip_data.network.network_address), + 'type': address_type.get(ip_data.version).lower(), + } + ) + + +snmp_section_inv_ip_address = SNMPSection( + name='inv_ip_addresses', + parse_function=parse_inv_ip_addresses, + host_label_function=host_label_inv_ip_addresses, + fetch=[ + SNMPTree( + base='.1.3.6.1.2.1.4.20.1', # IP-MIB::ipAddrEntry + oids=[ + '1', # ipAdEntAddr + '2', # ipAdEntIfIndex + '3', # ipAdEntNetMask + ] + ), + SNMPTree( + base='.1.3.6.1.2.1.4.34.1', # IP-MIB::ipAddrEntry + oids=[ + OIDEnd(), # type.length.ip-address + OIDBytes('2'), # ipAddressAddr + '3', # ipAddressIfIndex + '5', # ipAddressPrefix + ] + ), + SNMPTree( + base='.1.3.6.1.2.1.31.1.1.1', # + oids=[ + OIDEnd(), # ifIndex + '1', # ifName + ]), + ], + detect=exists('.1.3.6.1.2.1.4.20.1.1.*'), # +) + +inventory_plugin_inv_ip_address = InventoryPlugin( + name='inv_ip_addresses', + inventory_function=inventory_ip_addresses, +) diff --git a/source/packages/inv_ip_address b/source/packages/inv_ip_address new file mode 100644 index 0000000000000000000000000000000000000000..e96434dfc53342e0b40dc3daf11f79f41257b9da --- /dev/null +++ b/source/packages/inv_ip_address @@ -0,0 +1,19 @@ +{'author': 'Th.L. (thl-cmk[at]outlook[dot]com)', + 'description': 'Adds the IP addresses of a device monitored via SNMP to the ' + 'inventory\n' + '\n' + 'Adds host labels:\n' + ' - nvdct/l3v4_host: This label is set to "yes" for all ' + 'devices with one IPv4 address except form 127.0.0.0/8\n' + ' - nvdct/l3v4_routing: This label is set to "yes" for all ' + 'devices with more than one IPv4 address except form ' + '127.0.0.0/8\n', + 'download_url': 'https://thl-cmk.hopto.org', + 'files': {'cmk_addons_plugins': ['inv_ip_address/agent_based/inv_ip_addresses.py'], + 'web': ['plugins/views/inv_ip_addresses.py']}, + 'name': 'inv_ip_address', + 'title': 'Inventory of IP addresses', + 'version': '0.0.5-20241209', + 'version.min_required': '2.3.0b1', + 'version.packaged': 'cmk-mkp-tool 0.2.0', + 'version.usable_until': '2.4.0b1'} diff --git a/source/packages/inv_ipv4_addresses b/source/packages/inv_ipv4_addresses deleted file mode 100644 index c6d34da0631bf341ee96d0541bf714fdb1561cf1..0000000000000000000000000000000000000000 --- a/source/packages/inv_ipv4_addresses +++ /dev/null @@ -1,12 +0,0 @@ -{'author': 'Th.L. (thl-cmk[at]outlook[dot]com)', - 'description': 'Adds the IPv4 addresses of a device monitored via SNMP to the ' - 'inventory\n', - 'download_url': 'https://thl-cmk.hopto.org', - 'files': {'agent_based': ['inv_ipv4_addresses.py'], - 'web': ['plugins/views/inv_ipv4_addresses.py']}, - 'name': 'inv_ipv4_addresses', - 'title': 'Inventory of IPv4 addresses', - 'version': '0.0.4-20240407', - 'version.min_required': '2.2.0b1', - 'version.packaged': 'cmk-mkp-tool 0.2.0', - 'version.usable_until': '2.4.0b1'} diff --git a/source/web/plugins/views/inv_ipv4_addresses.py b/source/web/plugins/views/inv_ip_addresses.py similarity index 92% rename from source/web/plugins/views/inv_ipv4_addresses.py rename to source/web/plugins/views/inv_ip_addresses.py index b6fbf8877bb2e073228763741c7295dceb71fedb..81afc041eefdc5dffe42436a7466816453fe759b 100644 --- a/source/web/plugins/views/inv_ipv4_addresses.py +++ b/source/web/plugins/views/inv_ip_addresses.py @@ -19,16 +19,16 @@ inventory_displayhints.update({ 'address', 'device', 'type', - 'cidr', - 'netmask', 'network', + 'netmask', + 'cidr', 'broadcast', ], 'view': 'invipaddresses_of_host', }, '.networking.addresses:*.address': {'title': _l('Address')}, '.networking.addresses:*.broadcast': {'title': _l('Broadcast')}, - '.networking.addresses:*.cidr': {'title': _l('CIDR'), }, # 'filter': FilterInvtableIDRange}, + '.networking.addresses:*.cidr': {'title': _l('Prefix'), }, # 'filter': FilterInvtableIDRange}, '.networking.addresses:*.device': {'title': _l('Device')}, '.networking.addresses:*.netmask': {'title': _l('Netmask')}, '.networking.addresses:*.network': {'title': _l('Network')},