From aeaa238199c63b4512997ce728f28f56147f33ca Mon Sep 17 00:00:00 2001
From: "th.l" <thl-cmk@outlook.com>
Date: Mon, 9 Dec 2024 14:19:05 +0100
Subject: [PATCH] added support for IPv6, changed hostlabel to
 nvdct/l3v4_topology, rewritten for check API 2.0

---
 README.md                                     |   2 +-
 mkp/inv_ip_address-0.0.5-20241209.mkp         | Bin 0 -> 3610 bytes
 source/agent_based/inv_ipv4_addresses.py      | 164 -----------
 .../agent_based/inv_ip_addresses.py           | 275 ++++++++++++++++++
 source/packages/inv_ip_address                |  19 ++
 source/packages/inv_ipv4_addresses            |  12 -
 ..._ipv4_addresses.py => inv_ip_addresses.py} |   6 +-
 7 files changed, 298 insertions(+), 180 deletions(-)
 create mode 100644 mkp/inv_ip_address-0.0.5-20241209.mkp
 delete mode 100644 source/agent_based/inv_ipv4_addresses.py
 create mode 100644 source/cmk_addons_plugins/inv_ip_address/agent_based/inv_ip_addresses.py
 create mode 100644 source/packages/inv_ip_address
 delete mode 100644 source/packages/inv_ipv4_addresses
 rename source/web/plugins/views/{inv_ipv4_addresses.py => inv_ip_addresses.py} (92%)

diff --git a/README.md b/README.md
index 1f83346..ee96488 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
GIT binary patch
literal 3610
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

literal 0
HcmV?d00001

diff --git a/source/agent_based/inv_ipv4_addresses.py b/source/agent_based/inv_ipv4_addresses.py
deleted file mode 100644
index 8a90ed5..0000000
--- 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 0000000..92b6d60
--- /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 0000000..e96434d
--- /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 c6d34da..0000000
--- 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 b6fbf88..81afc04 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')},
-- 
GitLab