diff --git a/README.md b/README.md index 0b3d2b2642c3467d660a7b46489f1acc08b7ea0f..97eea0922a137ca0e0e41a1d876bd9ba83fe9923 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[PACKAGE]: ../../raw/master/mkp/inv_ip_address-0.0.5-20241209.mkp "inv_ip_address-0.0.5-20241209.mkp" +[PACKAGE]: ../../raw/master/mkp/inv_ip_address-0.0.6-20241210.mkp "inv_ip_address-0.0.6-20241210.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.6-20241210.mkp b/mkp/inv_ip_address-0.0.6-20241210.mkp new file mode 100644 index 0000000000000000000000000000000000000000..ea80d563999a5f6224b77e4a24475640e770b585 Binary files /dev/null and b/mkp/inv_ip_address-0.0.6-20241210.mkp differ 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 index 92b6d60d699b316562661112bea3f4256b60ef86..7ccce2010c8ba713c55db2af94dc2f2166d98128 100644 --- 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 @@ -17,8 +17,12 @@ # 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-06: incompatible: changed host label to nvdct/l3v4_topology:host and nvdct/l3v4_topology:router # 2024-12-09: rewritten for CMK checkAPI 2.0 +# 2024-12-10: fixed crash in host label function (AttributeError ('dict_values' object has no attribute 'version')) +# added support for ipv6z address type +# fixed duplicate ip information in section +# added host label nvdct/l3v6_topology:host and nvdct/l3v6_topology:router from collections.abc import Mapping, MutableSequence, Sequence from ipaddress import AddressValueError, NetmaskValueError, ip_interface @@ -130,6 +134,9 @@ def parse_inv_ip_addresses(string_table: List[StringByteTable]) -> Section: except ValueError: continue + if oid_end.startswith('4.20.254.128.'): # ipv6z, link local + ip_prefix = '64' + if (prefix := ip_prefix.split('.')[-1]) == '0': # drop entries without prefix (0) -> fortinet continue @@ -156,21 +163,36 @@ def parse_inv_ip_addresses(string_table: List[StringByteTable]) -> Section: ) case '39': raw_address = ''.join([chr(int(x)) for x in raw_address.split('.')]) + case '4': # ipv6z + # [ + # ['4.20.254.128.0.0.0.0.0.0.1.146.1.104.0.16.1.65.18.0.0.2', [], '1', '.0.0'], + # ['4.20.254.128.0.0.0.0.0.0.1.146.1.104.0.16.1.65.18.0.0.3', [], '2', '.0.0'] + # ] + # IP-MIB::ipAddressIfIndex.ipv6z."fe:80:00:00:00:00:00:00:01:92:01:68:00:10:01:41%301989890" = INTEGER: 1 + # IP-MIB::ipAddressIfIndex.ipv6z."fe:80:00:00:00:00:00:00:01:92:01:68:00:10:01:41%301989891" = INTEGER: 2 + match raw_length: + case '20': + raw_address = [f'{int(x):02x}' for x in raw_address.split('.')] + scope_id = '.'.join(raw_address[16:]) + raw_address = ':'.join( + [''.join([raw_address[i], raw_address[i + 1]]) for i in range(0, len(raw_address) - 4, 2)] + ) + raw_address += f'%{scope_id}' case _: continue try: - interface = ip_interface(f'{raw_address}/{prefix}') + interface_ip = ip_interface(f'{raw_address}/{prefix}') except (AddressValueError, NetmaskValueError): continue - if interface.ip.is_loopback: # Drop localhost + if interface_ip.ip.is_loopback: # Drop localhost continue - if interface.ip.exploded == '0.0.0.0': # drop this host address + if interface_ip.ip.exploded == '0.0.0.0': # drop this host address continue - ip_infos.append({(str(interface_by_index.get(if_index, if_index))): interface}) + ip_infos.append({(str(interface_by_index.get(if_index, if_index))): interface_ip}) for entry in ip_info_20: try: @@ -179,17 +201,18 @@ def parse_inv_ip_addresses(string_table: List[StringByteTable]) -> Section: continue try: - interface = ip_interface(f'{raw_address}/{raw_netmask}') + interface_ip = ip_interface(f'{raw_address}/{raw_netmask}') except (AddressValueError, NetmaskValueError): continue - if interface.ip.is_loopback: # Drop localhost + if interface_ip.ip.is_loopback: # Drop localhost continue - if interface.ip.exploded == '0.0.0.0': # drop this host address + if interface_ip.ip.exploded == '0.0.0.0': # drop this host address continue - ip_infos.append({str(interface_by_index.get(if_index, if_index)): interface}) + if not (ip_info := {str(interface_by_index.get(if_index, if_index)): interface_ip}) in ip_infos: + ip_infos.append(ip_info) return ip_infos @@ -199,19 +222,33 @@ 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 + "host" is set for all devices with one IPv4 address + "router" is set for all devices with more than one IPv4 address. + nvdct/l3v6_topology: + "host" is set for all devices with one IPv6 address + "router" is set for all devices with more than one IPv6 address. + + Link-local ("FE80::/64), unspecified ("::") and local-host ("127.0.0.0/8", "::1") IPs don't count. """ - 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 + + valid_v4_ips = 0 + valid_v6_ips = 0 + for interface_ips in section: + for interface_ip in interface_ips.values(): + if interface_ip.version == 4 and not interface_ip.is_loopback: + valid_v4_ips += 1 + if valid_v4_ips == 1: + yield HostLabel(name="nvdct/l3v4_topology", value="host") + if valid_v4_ips == 2: + yield HostLabel(name="nvdct/l3v4_topology", value="router") + + elif interface_ip.version == 6 and not interface_ip.is_loopback \ + and not interface_ip.is_link_local and not interface_ip.is_unspecified: + valid_v6_ips += 1 + if valid_v6_ips == 1: + yield HostLabel(name="nvdct/l3v6_topology", value="host") + if valid_v6_ips == 2: + yield HostLabel(name="nvdct/l3v6_topology", value="router") def inventory_ip_addresses(section: Section) -> InventoryResult: @@ -221,6 +258,11 @@ def inventory_ip_addresses(section: Section) -> InventoryResult: } for entry in section: for if_name, ip_data in entry.items(): + try: # ipv4 has no scope_id + scope_id = ip_data.scope_id + except AttributeError: + scope_id = None + yield TableRow( path=['networking', 'addresses'], key_columns={ @@ -233,6 +275,7 @@ def inventory_ip_addresses(section: Section) -> InventoryResult: 'netmask': str(ip_data.network.netmask), 'network': str(ip_data.network.network_address), 'type': address_type.get(ip_data.version).lower(), + **({"scope_id": str(scope_id)} if scope_id else {}), } ) @@ -273,3 +316,4 @@ 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 index e96434dfc53342e0b40dc3daf11f79f41257b9da..db97744ad45ce1eabd73f3dbef707aa91caacd05 100644 --- a/source/packages/inv_ip_address +++ b/source/packages/inv_ip_address @@ -13,7 +13,7 @@ 'web': ['plugins/views/inv_ip_addresses.py']}, 'name': 'inv_ip_address', 'title': 'Inventory of IP addresses', - 'version': '0.0.5-20241209', + 'version': '0.0.6-20241210', 'version.min_required': '2.3.0b1', 'version.packaged': 'cmk-mkp-tool 0.2.0', 'version.usable_until': '2.4.0b1'} diff --git a/source/web/plugins/views/inv_ip_addresses.py b/source/web/plugins/views/inv_ip_addresses.py index 81afc041eefdc5dffe42436a7466816453fe759b..84c70aba617447679d942bb6c0096269acc778d1 100644 --- a/source/web/plugins/views/inv_ip_addresses.py +++ b/source/web/plugins/views/inv_ip_addresses.py @@ -28,9 +28,10 @@ inventory_displayhints.update({ }, '.networking.addresses:*.address': {'title': _l('Address')}, '.networking.addresses:*.broadcast': {'title': _l('Broadcast')}, - '.networking.addresses:*.cidr': {'title': _l('Prefix'), }, # 'filter': FilterInvtableIDRange}, + '.networking.addresses:*.cidr': {'title': _l('Prefix Length'), }, # 'filter': FilterInvtableIDRange}, '.networking.addresses:*.device': {'title': _l('Device')}, '.networking.addresses:*.netmask': {'title': _l('Netmask')}, '.networking.addresses:*.network': {'title': _l('Network')}, - '.networking.addresses:*.type': {'title': _l('Address Type'), 'paint': 'ip_address_type'}, + '.networking.addresses:*.type': {'title': _l('Type'), 'paint': 'ip_address_type'}, + '.networking.addresses:*.scope_id': {'title': _l('Scope ID')}, })