From cf6919008f783677caae798071e3f4ec46ad2bd3 Mon Sep 17 00:00:00 2001
From: "th.l" <thl-cmk@outlook.com>
Date: Mon, 30 Dec 2024 11:29:19 +0100
Subject: [PATCH] refactoring/cleanup   - streamlining topology log messages  
 added:    --adjust-toml    --display-l2-neighbours    --include-l3-loopback  
  --skip-l3-cidr-0    --skip-l3-cidr-32-128    --skip-l3-public   fixed:   
 --dont-compare    --keep   changed:    L2_DROP_HOSTS -> L2_DROP_NEIGHBOURS  
 removed:    CUSTOM_LAYERS

---
 README.md                          |   2 +-
 mkp/nvdct-0.9.7-20241230.mkp       | Bin 0 -> 47429 bytes
 source/bin/nvdct/conf/nvdct.toml   |  47 +-
 source/bin/nvdct/lib/args.py       | 207 ++++---
 source/bin/nvdct/lib/backends.py   | 169 ++++--
 source/bin/nvdct/lib/constants.py  | 327 +++++++---
 source/bin/nvdct/lib/settings.py   | 349 ++++++-----
 source/bin/nvdct/lib/topologies.py | 946 ++++++++++++++---------------
 source/bin/nvdct/lib/utils.py      | 259 ++++----
 source/bin/nvdct/nvdct.py          | 150 +++--
 source/packages/nvdct              |   2 +-
 11 files changed, 1373 insertions(+), 1085 deletions(-)
 create mode 100644 mkp/nvdct-0.9.7-20241230.mkp

diff --git a/README.md b/README.md
index f4a9179..9ba3b1d 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-[PACKAGE]: ../../raw/master/mkp/nvdct-0.9.6-20241222.mkp "nvdct-0.9.6-20241222.mkp"
+[PACKAGE]: ../../raw/master/mkp/nvdct-0.9.7-20241230.mkp "nvdct-0.9.7-20241230.mkp"
 # Network Visualization Data Creation Tool (NVDCT)
 
 This script creates the topology data file needed for the [Checkmk Exchange Network visualization](https://exchange.checkmk.com/p/network-visualization) plugin.\
diff --git a/mkp/nvdct-0.9.7-20241230.mkp b/mkp/nvdct-0.9.7-20241230.mkp
new file mode 100644
index 0000000000000000000000000000000000000000..6d8891a76db8f05493e079d6b60d689cc0533ed5
GIT binary patch
literal 47429
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
Z{$CEz|8<QbHRoPX%f9lHqtK$F{12KxT)Y4P

literal 0
HcmV?d00001

diff --git a/source/bin/nvdct/conf/nvdct.toml b/source/bin/nvdct/conf/nvdct.toml
index e907197..d31f64e 100755
--- a/source/bin/nvdct/conf/nvdct.toml
+++ b/source/bin/nvdct/conf/nvdct.toml
@@ -12,7 +12,7 @@
 # contains the user data and settings for nvdct.py
 #
 
-# list of (additional to -s/--seed-devices) seed devices
+# list of CDP/LLDP seed devices (if empty, all CDP/LLDP devices will be used)
 # [0-9-a-zA-Z\.\_\-]{1,253} -> host
 L2_SEED_DEVICES = [
     # "CORE01",
@@ -20,8 +20,8 @@ L2_SEED_DEVICES = [
     # "LOCATION02",
 ]
 
-# drop neighbours with invalid names (only L2 Topologies (i.e. CDP, LLDP, CUSTOM)
-L2_DROP_HOSTS = [
+# drop CDP/LLDP neighbours names
+L2_DROP_NEIGHBOURS = [
     # "not advertised",
     #  "a nother invalid name",
 ]
@@ -53,7 +53,7 @@ L3V4_IGNORE_WILDCARD = [
     # ["172.17.128.3", "0.0.127.0"],  # ignore all IPs ending with 3 from 172.17.128.0/17
 ]
 
-# networks to summarize
+# IP _networks_ to summarize
 L3_SUMMARIZE = [
    # "10.193.172.0/24",
    # "10.194.8.0/23",
@@ -82,17 +82,7 @@ STATIC_CONNECTIONS = [
     # connection: "left_host"<->"right_host"
 ]
 
-# THIS OPTION IS DEPRECATED
-# optional custom layers use option -l/--layers CUSTOM to include these layers
-# don't use --pre-fetch without a host_label that matches all host you want to add
-# THIS OPTION IS DEPRECATED
-CUSTOM_LAYERS = [
-#    { path = "path,in,inventory", columns = "columns from inventory", label = "label for the layer", host_label = "CMK host label to find matching hosts" },
-#    { path = "networking,lldp_cache,neighbours", columns = "neighbour_name,local_port,neighbour_port", label = "custom_LLDP", host_label = "nvdct/has_lldp_neighbours" },
-#    { path = "networking,cdp_cache,neighbours", columns = "neighbour_name,local_port,neighbour_port", label = "custom_CDP", host_label = "nvdct/has_cdp_neighbours" },
-]
-
-# list customers to include/excluse, use option --filter-costumers INCLUDE/EXCLUDE
+# list customers to include/excluse, use with option --filter-costumers INCLUDE/EXCLUDE
 # [0-9-a-zA-Z\.\_\-]{1,16} -> customer
 CUSTOMERS = [
     # "customer1",
@@ -100,7 +90,7 @@ CUSTOMERS = [
     # "customer3",
 ]
 
-# list site to include/excluse, use option --filter-sites INCLUDE/EXCLUDE
+# list site to include/excluse, use with option --filter-sites INCLUDE/EXCLUDE
 # [0-9-a-zA-Z\.\_\-]{1,16} -> site
 SITES = [
     # "site1",
@@ -108,20 +98,21 @@ SITES = [
     # "site3",
 ]
 
-# map inventory neighbour name to Checkmk host name
+# map inventory CDP/LLDP neighbour name to Checkmk host name
 # [0-9-a-zA-Z\.\_\-]{1,253} -> host
 [L2_HOST_MAP]
-# inventory_neighbour1 = "cmk_host1"
-# inventory_neighbour2 = "cmk_host2"
-# inventory_neighbour3 = "cmk_host3"
+# "inventory_neighbour1" = "cmk_host1"
+# "inventory_neighbour2" = "cmk_host2"
+# "inventory_neighbour3" = "cmk_host3"
 
+# modify CDP/LLDP neighbour name with regex before mapping to CMK host names
 [L2_NEIGHBOUR_REPLACE_REGEX]
 # "regex string to replace" = "string to replace with"
 # "^(([0-9a-fA-F]){2}[:.-]?){5}([0-9a-fA-F]){2}$" = ""
 # "\\([0-9a-zA-Z]+\\)$" = ""
 # "^Meraki.*\\s-\\s" = ""
 
-# replace network objects (takes place after summarize)
+# replace _network objects_ in L§ topologies (takes place after summarize)
 # [0-9-a-zA-Z\.\_\-]{1,253} -> host
 [L3_REPLACE]
 # "10.193.172.0/24" = "MPLS"
@@ -134,17 +125,18 @@ SITES = [
 # can use misc icons from CMK or upload your own in the misc category
 # for built-in icons use "icon_" as prefix to the name from CMK
 # max size 80x80px
-# "host_node" = "icon_missinc"
+# emblems will only be used for non CMK objects
+# "host_node" = "icon_alert_unreach"
 # "ip_address" = "ip-address_80"
 # "ip_network" = "ip-network_80"
 # "l3_replace" = "icon_plugins_cloud"
 # "l3_summarize" = "icon_aggr"
-# "service_node" = "icon_missing"
+# "service_node" = "icon_alert_unreach"
 
 [MAP_SPEED_TO_THICKNESS]
 # must be sorted from slower to faster speed
-# use only one entry to have all conections with the same thickness
-# bits per second = thickness
+# use only one/no entry to have all conections with the same thickness
+# "bits per second" = thickness
 # "2000000" = 1  # 2 mbit
 # "5000000" = 2  # 5 mbit
 # "1e7" = 3      # 10 mbit
@@ -158,10 +150,12 @@ SITES = [
 # backend = "MULTISITE" | "RESTAPI" | "LIVESTATUS"
 # case = "LOWER" | "UPPER"
 # default = false
+# display_l2_neighbours = false
 # dont_compare = false
 # filter_customers = "INCLUDE" |"EXCLUDE"
 # filter_sites = "INCLUDE" | "EXCLUDE"
 # include_l3_hosts = false
+# include_l3_loopback = false  # most likely dropped from inventory (SNMP) before
 keep = 10
 # layers = ["LLDP", "CDP", "L3v4", "STATIC", "CUSTOM"]
 # log_file = "~/var/log/nvdct.log"
@@ -173,6 +167,9 @@ output_directory = 'nvdct'  # remove to get date formated directory
 # prefix = ""
 # quiet = true
 # remove_domain = false
+# skip_l3_cidr_0 = false
+# skip_l3_cidr_32_128 = false
 # skip_l3_if = false
 # skip_l3_ip = false
+# skip_l3_public = false
 # time_format = "%Y-%m-%dT%H:%M:%S.%m"
diff --git a/source/bin/nvdct/lib/args.py b/source/bin/nvdct/lib/args.py
index 5ad06bd..1d1be87 100755
--- a/source/bin/nvdct/lib/args.py
+++ b/source/bin/nvdct/lib/args.py
@@ -11,18 +11,19 @@
 # options used
 # -b --backend
 # -d --default
-# -l --layer
+# -l --layers
 # -o --output-directory
 # -p --prefix
-# # -s --seed-devices
 # -u --user-data-file
 # -v --version
 # --api-port (deprecated ?)
 # --case
 # --check-user-data-only
+# --display-l2-neighbours
 # --dont-compare
 # --filter-customers
 # --filter-sites
+# --fix-toml
 # --include-l3-hosts
 # --keep
 # --log-file
@@ -45,16 +46,19 @@ from argparse import (
 from pathlib import Path
 
 from lib.constants import (
+    Backends,
+    Case,
+    CliLong,
     ExitCodes,
-    HOME_URL,
-    MIN_CDP_VERSION,
-    MIN_LINUX_IP_ADDRESSES,
-    MIN_LLDP_VERSION,
-    MIN_SNMP_IP_ADDRESSES,
-    MIN_WINDOWS_IP_ADDRESSES,
+    IncludeExclude,
+    Layers,
+    LogLevels,
+    MinVersions,
     NVDCT_VERSION,
     SCRIPT,
     TIME_FORMAT_ARGPARSER,
+    TomlSections,
+    URLs,
     USER_DATA_FILE,
 )
 
@@ -63,20 +67,15 @@ def parse_arguments() -> arg_Namespace:
     parser = ArgumentParser(
         prog='nvdct.py',
         description=(
-            'This script creates the topology data file needed for the Checkmk "network_visualization"\n'  # noqa: E501
-            'plugin by Andreas Boesl and schnetz. For more information see\n'
-            'the announcement from schnetz: '
-            'https://forum.checkmk.com/t/network-visualization/41680\n'
-            'and the plugin on the Exchange: '
-            'https://exchange.checkmk.com/p/network-visualization .\n'
+            'This script creates the topology data file needed for the Checkmk Network Visualization,\n'  # noqa: E501
+            'For more information see the announcement from schnetz in the Checkmk forum:\n'
+            f'{URLs.FORUM_SCHNETZ}\n'
             '\n'
-            'The required inventory data can be created with my inventory plugins:\n'
-            'CDP: https://thl-cmk.hopto.org/gitlab/checkmk/vendor-independent/inventory/inv_cdp_cache\n'  # noqa: E501
-            'LLDP: https://thl-cmk.hopto.org/gitlab/checkmk/vendor-independent/inventory/inv_lldp_cache\n'  # noqa: E501
-            'L3v4: https://thl-cmk.hopto.org/gitlab/checkmk/vendor-independent/inventory/inv_ip_address\n'  # noqa: E501
+            'The required plugins to create the inventory data can be found here:\n'
+            f'{URLs.TOPIC_NV}\n'
             '\n'
             f'\nVersion: {NVDCT_VERSION} | Written by: thl-cmk\n'
-            f'for more information see: {HOME_URL}'
+            f'for more information see: {URLs.NVDCT}'
         ),
         formatter_class=RawTextHelpFormatter,
         epilog='Exit codes:\n'
@@ -91,156 +90,180 @@ def parse_arguments() -> arg_Namespace:
     )
 
     parser.add_argument(
-        '-b', '--backend',
-        choices=['LIVESTATUS', 'MULTISITE', 'RESTAPI'],
+        '-b', CliLong.BACKEND,
+        choices=[Backends.LIVESTATUS, Backends.MULTISITE, Backends.RESTAPI],
         # default='MULTISITE',
         help='Backend used to retrieve the topology data\n'
-             ' - LIVESTATUS : fetches data via local Livestatus (local site only)\n'
-             ' - MULTISITE  : like LIVESTATUS but for distributed environments (default)\n'
-             ' - RESTAPI    : uses the CMK REST API.',
+             f' - {Backends.LIVESTATUS} : fetches data via local Livestatus (local site only)\n'
+             f' - {Backends.MULTISITE}  : like LIVESTATUS but for distributed environments (default)\n'
+             f' - {Backends.RESTAPI}    : uses the CMK REST API.',
     )
     parser.add_argument(
-        '-d', '--default', action='store_const', const=True,  # default=False,
+        '-d', CliLong.DEFAULT, action='store_const', const=True,  # default=False,
         help='Set the created topology data as default. Will be created automatically\n'
-             'if it doesn\'t exists.',
+             'if it doesnt exists.',
     )
     parser.add_argument(
-        '-o', '--output-directory', type=str,
+        '-o', CliLong.OUTPUT_DIRECTORY, type=str,
         help='Directory name where to save the topology data.\n'
              'I.e.: my_topology. Default is the actual date/time\n'
-             'in "--time-format" format.\n'
+             f'in "{CliLong.TIME_FORMAT}" format.\n'
              'NOTE: the directory is a sub directory under "~/var/check_mk/topology/data/"\n',
     )
-    # parser.add_argument(
-    #     '-s', '--seed-devices', type=str, nargs='+',
-    #     help=f'List of devices to start the topology discovery from.\n'
-    #          f'I.e. {SAMPLE_SEEDS}',
-    # )
     parser.add_argument(
-        '-p', '--prefix', type=str,
-        help='Prepends each host with the prefix. (Needs testing)\n'
+        '-p', CliLong.PREFIX, type=str,
+        help='Prepends each host with the prefix. (Needs more testing)\n'
     )
     parser.add_argument(
-        '-l', '--layers',
+        '-l', CliLong.LAYERS,
         nargs='+',
         choices=[
-            'CDP',
-            'CUSTOM',
-            'L3v4',
-            'LLDP',
-            'STATIC',
+            Layers.CDP,
+            Layers.LLDP,
+            Layers.L3V4,
+            Layers.STATIC,
         ],
         # default=['CDP'],
         help=(
-            f' - CDP      : needs inv_cdp_cache package at least in version {MIN_CDP_VERSION}\n'
-            f' - LLDP     : needs inv_lldp_cache package at least in version {MIN_LLDP_VERSION}\n'
-            f' - L3v4     : needs inv_ip_address package at least in version {MIN_SNMP_IP_ADDRESSES} for SNMP based hosts\n'
-            f'              for Linux based hosts inv_lnx_ip_if in version {MIN_LINUX_IP_ADDRESSES}\n'
-            f'              for Windows based hosts inv_win_ip_if in version {MIN_WINDOWS_IP_ADDRESSES}\n'
-            f' - STATIC   : creates a topology base on the "STATIC_CONNECTIONS" in the toml file\n'
-            f' - CUSTOM   : (deprecated)\n'
+            f' - {Layers.CDP}      : needs inv_cdp_cache package at least in version {MinVersions.CDP}\n'
+            f' - {Layers.LLDP}     : needs inv_lldp_cache package at least in version {MinVersions.LLDP}\n'
+            f' - {Layers.L3V4}     : needs inv_ip_address package at least in version {MinVersions.SNMP_IP_ADDRESSES} for SNMP based hosts\n'
+            f'              for Linux based hosts inv_lnx_ip_if in version {MinVersions.LINUX_IP_ADDRESSES}\n'
+            f'              for Windows based hosts inv_win_ip_if in version {MinVersions.WINDOWS_IP_ADDRESSES}\n'
+            f' - {Layers.STATIC}   : creates a topology base on the "[{TomlSections.STATIC_CONNECTIONS}]" section in the TOML file\n'
         )
     )
     parser.add_argument(
-        '-u', '--user-data-file', type=str,
-        help='Set the name uf the user provided data file\n'
+        '-u', CliLong.USER_DATA_FILE, type=str,
+        help='Set the name of the user provided data file\n'
              f'Default is ~/local/bin/nvdct/conf/{USER_DATA_FILE}\n',
     )
     parser.add_argument(
-        '-v', '--version', action='version',
+        '-v', CliLong.VERSION, action='version',
         version=f'{Path(SCRIPT).name} version: {NVDCT_VERSION}',
         help='Print version of this script and exit',
     )
     parser.add_argument(
-        '--api-port', type=int,  # default=False,
-        help='TCP Port to access the REST API. Default is 80. NVDCT will try to automatically\n'
+        CliLong.ADJUST_TOML, action='store_const', const=True,  # default=False,
+        help='Adjusts old options in TOML file.',
+    )
+    parser.add_argument(
+        CliLong.API_PORT, type=int,  # default=False,
+        help='TCP Port to access the REST API. By NVDCT will try to automatically\n'
              'detect the site apache port.',
     )
     parser.add_argument(
-        '--case',
-        choices=['LOWER', 'UPPER'],
+        CliLong.CASE,
+        choices=[Case.LOWER, Case.UPPER],
         # default='NONE',
-        help='Change neighbour name to all lower/upper case',
+        help='Change L2 neighbour name to all lower/upper case before matching to CMK host',
     )
     parser.add_argument(
-        '--check-user-data-only', action='store_const', const=True,  # default=False,
+        CliLong.CHECK_USER_DATA_ONLY, action='store_const', const=True,  # default=False,
         help=f'Only tries to read/parse the user data from {USER_DATA_FILE} and exits.',
     )
     parser.add_argument(
-        '--log-file', type=str,
-        help='Set the log file. Default is ~/var/log/nvdct.log\n',
+        CliLong.LOG_FILE, type=str,
+        help='Set the log file. Default is "~/var/log/nvdct.log"\n',
     )
     parser.add_argument(
-        '--log-level',
+        CliLong.LOG_LEVEL,
         # nargs='+',
-        choices=['CRITICAL', 'FATAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG', 'OFF'],
+        choices=[
+            LogLevels.CRITICAL,
+            LogLevels.FATAL,
+            LogLevels.ERROR,
+            LogLevels.WARNING,
+            LogLevels.INFO,
+            LogLevels.DEBUG,
+            LogLevels.OFF
+        ],
         # default='WARNING',
-        help='Sets the log level. The default is "WARNING"\n'
+        help=f'Sets the log level. The default is "{LogLevels.WARNING}"\n'
     )
     parser.add_argument(
-        '--log-to-stdout', action='store_const', const=True,  # default=False,
+        CliLong.LOG_TO_STDOUT, action='store_const', const=True,  # default=False,
         help='Send log to stdout.',
     )
     parser.add_argument(
-        '--dont-compare', action='store_const', const=True,  # default=False,
+        CliLong.DISPLAY_L2_NEIGHBOURS, action='store_const', const=True,  # default=False,
+        help='Use L2 neighbour name as display name in L2 topologies',
+    )
+    parser.add_argument(
+        CliLong.DONT_COMPARE, action='store_const', const=True,  # default=False,
         help='Do not compare the actual topology data with the default topology\n'
              'data. By default, the actual topology is compared with the default\n'
              'topology. If the data matches, the actual topology is not saved.\n'
              'So, if you run this tool in a cron job, a new topology will be\n'
-             'created only if there was a change, unless you use "--dont-compare".'
+             f'created only if there was a change, unless you use "{CliLong.DONT_COMPARE}".'
     )
     parser.add_argument(
-        '--filter-customers',
-        choices=['INCLUDE', 'EXCLUDE'],
+        CliLong.FILTER_CUSTOMERS,
+        choices=[IncludeExclude.INCLUDE, IncludeExclude.EXCLUDE],
         # default='INCLUDE',
-        help='INCLUDE/EXCLUDE customer list from config file.\n'
-             'Note: MULTISITE backend only.',
+        help=f'{IncludeExclude.INCLUDE}/{IncludeExclude.EXCLUDE} customer list "[{TomlSections.CUSTOMERS}]" from TOML file.\n'
+             f'Note: {Backends.MULTISITE} backend only.',
     )
     parser.add_argument(
-        '--filter-sites',
-        choices=['INCLUDE', 'EXCLUDE'],
+        CliLong.FILTER_SITES,
+        choices=[IncludeExclude.EXCLUDE, IncludeExclude.EXCLUDE],
         # default='INCLUDE',
-        help='INCLUDE/EXCLUDE site list from config file.\n'
-             'Note: MULTISITE backend only.',
+        help=f'{IncludeExclude.INCLUDE}/{IncludeExclude.EXCLUDE} site list "[{TomlSections.SITES}]" from TOML file.\n'
+    )
+    parser.add_argument(
+        CliLong.INCLUDE_L3_HOSTS, action='store_const', const=True,  # default=False,
+        help='Include hosts (single IP objects) in layer 3 topologies',
     )
     parser.add_argument(
-        '--include-l3-hosts', action='store_const', const=True,  # default=False,
-        help='Include hosts (single IP objects) in layer 3 topology',
+        CliLong.INCLUDE_L3_LOOPBACK, action='store_const', const=True,  # default=False,
+        help='Include loopback ip-addresses in layer 3 topologies',
     )
     parser.add_argument(
-        '--remove-domain', action='store_const', const=True,  # default=False,
-        help='Remove the domain name from the neighbor name',
+        CliLong.REMOVE_DOMAIN, action='store_const', const=True,  # default=False,
+        help='Remove the domain name from the L2 neighbor name before matching CMK host.',
     )
     parser.add_argument(
-        '--keep', type=int,
+        CliLong.KEEP, type=int,
         help='Number of topologies to keep. The oldest topologies above keep\n'
-             'max will be deleted.\n'
-             'NOTE: The default topologies will be always kept.\n'
+             'will be deleted.\n'
+             'NOTE: The default/protected topologies will be kept always.\n'
     )
     parser.add_argument(
-        '--min-age', type=int,
-        help='The minimum number of days before a topology is deleted by "--keep".'
+        CliLong.MIN_AGE, type=int,
+        help=f'The minimum number of days before a topology is deleted by "{CliLong.KEEP}"'
     )
     parser.add_argument(
-        '--pre-fetch', action='store_const', const=True,  # default=False,
-        help='Try to fetch host data, with less API calls. Can improve RESTAPI backend\n'
+        CliLong.PRE_FETCH, action='store_const', const=True,  # default=False,
+        help=f'Try to fetch host data, with less API calls. Can improve {Backends.RESTAPI} backend\n'
              'performance',
     )
     parser.add_argument(
-        '--quiet', action='store_const', const=True,  # default=False,
-        help='Suppress output to stdtout',
+        CliLong.QUIET, action='store_const', const=True,  # default=False,
+        help='Suppress all output to stdtout',
+    )
+    parser.add_argument(
+        CliLong.SKIP_L3_CIDR_0, action='store_const', const=True,  # default=False,
+        help='Skip ip-address with CIDR "/0" in layer 3 topologies',
+    )
+    parser.add_argument(
+        CliLong.SKIP_L3_CIDR_32_128, action='store_const', const=True,  # default=False,
+        help='Skip ip-address with CIDR "/32" or "/128" in layer 3 topologies',
+    )
+    parser.add_argument(
+        CliLong.SKIP_L3_IF, action='store_const', const=True,  # default=False,
+        help='Dont show interface in layer 3 topologies',
     )
     parser.add_argument(
-        '--skip-l3-if', action='store_const', const=True,  # default=False,
-        help='Skip interface in layer 3 topology',
+        CliLong.SKIP_L3_IP, action='store_const', const=True,  # default=False,
+        help='Dont show ip-addresses in layer 3 topologies',
     )
     parser.add_argument(
-        '--skip-l3-ip', action='store_const', const=True,  # default=False,
-        help='Skip ip-address in layer 3 topology',
+        CliLong.SKIP_L3_PUBLIC, action='store_const', const=True,  # default=False,
+        help='Skip public ip-addresses in layer 3 topologies',
     )
     parser.add_argument(
-        '--time-format', type=str,
-        help=f'Format string to render the time. (default: {TIME_FORMAT_ARGPARSER})',
+        CliLong.TIME_FORMAT, type=str,
+        help=f'Format string to render the time. (default: "{TIME_FORMAT_ARGPARSER}")',
     )
 
     return parser.parse_args()
diff --git a/source/bin/nvdct/lib/backends.py b/source/bin/nvdct/lib/backends.py
index 78a7d4b..80adf5a 100755
--- a/source/bin/nvdct/lib/backends.py
+++ b/source/bin/nvdct/lib/backends.py
@@ -16,19 +16,23 @@
 from abc import abstractmethod
 from ast import literal_eval
 from collections.abc import Mapping, MutableSequence, Sequence
-from enum import Enum, unique
 from pathlib import Path
 from requests import session
 from sys import exit as sys_exit
-from typing import Dict, List, Tuple
+from typing import Dict, List, Tuple, MutableMapping
 
 from livestatus import MultiSiteConnection, SiteConfigurations, SiteId
 
 from lib.constants import (
+    Backends,
     CACHE_INTERFACES_DATA,
+    CacheItems,
+    Case,
     ExitCodes,
+    IncludeExclude,
+    InvPaths,
     OMD_ROOT,
-    PATH_INTERFACES,
+    TomlSections,
 )
 from lib.utils import (
     LOGGER,
@@ -84,36 +88,45 @@ def hosts_to_query(hosts: List[str]) -> Tuple[str, List[str]]:
 
     return hosts_str, open_hosts
 
-
-@unique
-class CacheItems(Enum):
-    inventory = 'inventory'
-    interfaces = 'interfaces'
-
-    def __get__(self, instance, owner):
-        return self.value
-
 class HostCache:
     def __init__(
             self,
-            pre_fetch: bool,
             backend: str,
+            pre_fetch: bool,
     ):
         LOGGER.info('init HOST_CACHE')
 
         self.cache: Dict = {}
+        self.neighbour_to_host: MutableMapping[str, str] = {}
         self._inventory_pre_fetch_list: List[str] = [
-            PATH_INTERFACES,
+            InvPaths.INTERFACES,
         ]
 
-        self.pre_fetch: bool = bool(pre_fetch)
         self.backend: str = str(backend)
+        self.case: str = ''
+        self.l2_host_map: Dict[str, str] = {}
+        self.l2_neighbour_replace_regex: List[Tuple[str, str]] = []
+        self.pre_fetch: bool = bool(pre_fetch)
+        self.prefix: str = ''
+        self.remove_domain: bool = False
 
         if self.pre_fetch:
             for host in self.query_all_hosts():
                 self.cache[host] = HOST_EXIST.copy()
 
-    def get_inventory_data(self, hosts: Sequence[str]) -> Dict[str, Dict]:
+    def init_neighbour_to_host(
+            self,
+            case: str,
+            l2_host_map: Dict[str, str],
+            prefix: str,
+            remove_domain: bool,
+    ):
+        self.case: str = case
+        self.l2_host_map: Dict[str, str] = l2_host_map
+        self.prefix: str = prefix
+        self.remove_domain: bool = remove_domain
+
+    def get_inventory_data(self, hosts: List[str]) -> Dict[str, Dict]:
         """
         Returns a dictionary of hosts and there inventory data.
         Args:
@@ -136,7 +149,7 @@ class HostCache:
 
         return inventory_data
 
-    def get_interface_data(self, hosts: Sequence[str]) -> Dict[str, Dict | None]:
+    def get_interface_data(self, hosts: List[str]) -> Dict[str, Dict | None]:
         """
         Returns Dictionary of hosts and there interface services from CMK.
         The interface information consists of the "Item", the "Description (summary)" and the service details
@@ -185,7 +198,7 @@ class HostCache:
         """
         return self.query_hosts_by_label(label)
 
-    def fill_cache(self, hosts: Sequence[str]) -> None:
+    def fill_cache(self, hosts: List[str]) -> None:
         """
         Gets the host data from CMK and puts them in the host cache. Data collected:
         - inventory
@@ -218,13 +231,13 @@ class HostCache:
                 self.cache[host][CacheItems.interfaces] = {}
             self.cache[host][CacheItems.interfaces][CACHE_INTERFACES_DATA] = interfaces
 
-    def get_data(self, host: str, item: CacheItems, path: str) -> Dict[str, any] | None:
+    def get_data(self, host: str, item: CacheItems, path: str) -> Mapping | Sequence | None:
         """
         Returns data from self.cache. If the cache for "host" is empty, data will be fetched from CMK
         Args:
             host: host to get data from cache
             item: item in cache (inventory/interface)
-            path: path in cache item
+            path: path in cache to data
 
         Returns:
             the requested data or None
@@ -243,6 +256,62 @@ class HostCache:
     def add_inventory_path(self, path: str) -> None:
         self._inventory_pre_fetch_list = list(set(self._inventory_pre_fetch_list + [path]))
 
+    def get_host_from_neighbour(self, neighbour: str) -> str | None:
+        """
+        Tries to get the CMK host name from a L2 neighbour name. It will test:
+        - the neighbour without domain name
+        - map the neighbour to a host via L2_HOST_MAP
+        - the neighbour in UPPER case (without domain)
+        - the neighbour in lower case (including domain)
+        - the neighbour with prefix
+        Args:
+            neighbour: the L2 neighbour name to find a CMK host for
+
+        Returns:
+            The CMK host name for the L2 neighbour or None if no host is found
+
+        """
+        try:
+            return self.neighbour_to_host[neighbour]
+        except KeyError:
+            pass
+
+        host = neighbour
+
+        # rewrite neighbour if inventory neighbour and checkmk host don't match
+        if host in self.l2_host_map:
+            LOGGER.info(f'Replace neighbour by [{TomlSections.L2_HOST_MAP}]: {neighbour} -> {host}')
+            host = self.l2_host_map[host]
+
+        if self.remove_domain:
+            LOGGER.debug(f'Remove domain: {host} -> {host.split(".")[0]}')
+            host = host.split('.')[0]
+
+        match self.case:
+            case Case.UPPER:
+                LOGGER.debug(f'Change neighbour to upper case: {host} -> {host.upper()}')
+                host = host.upper()
+
+            case Case.LOWER:
+                LOGGER.debug(f'Change neighbour to lower case: {host} -> {host.lower()}')
+                host = host.lower()
+            case _:
+                pass
+
+        if self.prefix:
+            LOGGER.debug(f'Prepend neighbour with prefix: {host} -> {self.prefix}{host}')
+            host = f'{self.prefix}{host}'
+
+
+        if self.host_exists(host):
+            self.neighbour_to_host[neighbour] = host
+            LOGGER.debug(f'Matched neighbour to host: |{neighbour}| -> |{host}|')
+            return host
+        else:
+            self.neighbour_to_host[neighbour] = None
+            LOGGER.debug(f'No match found for neighbour: |{neighbour}|')
+            return None
+
     @abstractmethod
     def query_host(self, host: str) -> bool:
         """
@@ -285,8 +354,16 @@ class HostCache:
         raise NotImplementedError
 
 class HostCacheLiveStatus(HostCache):
-    def __init__(self, pre_fetch: bool, backend: str = '[LIVESTATUS]'):
-        super().__init__(pre_fetch, backend)
+    def __init__(
+            self,
+            pre_fetch: bool,
+            backend: str = f'[{Backends.LIVESTATUS}]',
+    ):
+        self.backend = backend
+        super().__init__(
+            pre_fetch = pre_fetch,
+            backend = self.backend,
+        )
 
     def get_raw_data(self, query: str) -> any:
         return get_data_form_live_status(query=query)
@@ -294,9 +371,9 @@ class HostCacheLiveStatus(HostCache):
     def query_host(self, host: str) -> bool:
         query = (
             'GET hosts\n'
-            'Columns: host_name\n'
+            'Columns: name\n'
             'OutputFormat: python3\n'
-            f'Filter: host_name = {host}\n'
+            f'Filter: name = {host}\n'
         )
         data: Sequence[Sequence[str]] = self.get_raw_data(query=query)
         LOGGER.debug(f'{self.backend} data for host {host}: {data}')
@@ -309,7 +386,7 @@ class HostCacheLiveStatus(HostCache):
     def query_all_hosts(self) -> Sequence[str]:
         query = (
             'GET hosts\n'
-            'Columns: host_name\n'
+            'Columns: name\n'
             'OutputFormat: python3\n'
         )
         data: Sequence[Sequence[str]] = self.get_raw_data(query=query)
@@ -339,9 +416,9 @@ class HostCacheLiveStatus(HostCache):
     def query_inventory_data(self, hosts: str) -> Dict[str, Dict]:
         query = (
             'GET hosts\n'
-            'Columns: host_name mk_inventory\n'
+            'Columns: name mk_inventory\n'
             'OutputFormat: python3\n'
-            f'Filter: host_name ~~ {hosts}\n'
+            f'Filter: name ~~ {hosts}\n'
         )
         inventory_data = {}
         data: Sequence[Tuple[str, bytes]] = self.get_raw_data(query=query)
@@ -386,11 +463,13 @@ class HostCacheMultiSite(HostCacheLiveStatus):
         self,
         pre_fetch: bool,
         filter_sites: str | None = None,
-        sites: List[str] = [],
+        sites: List[str] | None = None,
         filter_customers: str | None = None,
         customers: List[str] = None,
     ):
-        self.backend = '[MULTISITE]'
+        if not sites:
+            sites = []
+        self.backend = f'[{Backends.MULTISITE}]'
         self.sites: SiteConfigurations = SiteConfigurations({})
         self.get_sites()
         self.filter_sites(filter_sites, sites)
@@ -402,9 +481,12 @@ class HostCacheMultiSite(HostCacheLiveStatus):
         self.dead_sites = [site['site']['alias'] for site in self.c.dead_sites().values()]
         if self.dead_sites:
             dead_sites = ', '.join(self.dead_sites)
-            LOGGER.warning(f'{self.backend} WARNING: use of dead site(s) {dead_sites} is disabled')
+            LOGGER.warning(f'{self.backend} use of dead site(s) {dead_sites} is disabled')
             self.c.set_only_sites(self.c.alive_sites())
-        super().__init__(pre_fetch, self.backend)
+        super().__init__(
+            pre_fetch=pre_fetch,
+            backend=self.backend,
+        )
 
     def get_raw_data(self, query: str) -> object:
         return self.c.query(query=query)
@@ -459,20 +541,20 @@ class HostCacheMultiSite(HostCacheLiveStatus):
 
     def filter_sites(self, filter_: str | None, sites: List[str]):
         match filter_:
-            case 'INCLUDE':
+            case IncludeExclude.INCLUDE:
                 self.sites = {site: data for site, data in self.sites.items() if site in sites}
-            case 'EXCLUDE':
+            case IncludeExclude.EXCLUDE:
                 self.sites = {site: data for site, data in self.sites.items() if site not in sites}
             case _:
                 return
 
     def filter_costumers(self, filter_: str | None, costumers: List[str]):
         match filter_:
-            case 'INCLUDE':
+            case IncludeExclude.INCLUDE:
                 self.sites = {
                     site: data for site, data in self.sites.items() if data.get('customer') in costumers
                 }
-            case 'EXCLUDE':
+            case IncludeExclude.EXCLUDE:
                 self.sites = {
                     site: data for site, data in self.sites.items() if data.get('customer') not in costumers
                 }
@@ -485,9 +567,11 @@ class HostCacheRestApi(HostCache):
             pre_fetch: bool,
             api_port: int,
             filter_sites: str | None = None,
-            sites: List[str] = [],
+            sites: List[str] | None = None,
     ):
-        self.backend = '[RESTAPI]'
+        if not sites:
+            sites = []
+        self.backend = f'[{Backends.RESTAPI}]'
         LOGGER.debug(f'{self.backend} init backend')
 
         try:
@@ -513,7 +597,10 @@ class HostCacheRestApi(HostCache):
         self.sites: MutableSequence[str]  = self.query_sites()
         self.filter_sites(filter_=filter_sites, sites=sites)
         LOGGER.info(f'{self.backend} filtered sites : {self.sites}')
-        super().__init__(pre_fetch, self.backend)
+        super().__init__(
+            pre_fetch=pre_fetch,
+            backend=self.backend,
+        )
 
     def get_raw_data(self, url: str, params: Mapping[str, object] | None):
         resp = self.__session.get(
@@ -541,9 +628,9 @@ class HostCacheRestApi(HostCache):
 
     def filter_sites(self, filter_: str | None, sites: List[str]):
         match filter_:
-            case 'INCLUDE':
+            case IncludeExclude.INCLUDE:
                 self.sites = [site for site in self.sites if site in sites]
-            case 'EXCLUDE':
+            case IncludeExclude.EXCLUDE:
                 self.sites = [site for site in self.sites if site not in sites]
             case _:
                 return
@@ -652,4 +739,4 @@ class HostCacheRestApi(HostCache):
                         'long_plugin_output': long_plugin_output.split('\\n')
                     }
 
-        return interface_data
\ No newline at end of file
+        return interface_data
diff --git a/source/bin/nvdct/lib/constants.py b/source/bin/nvdct/lib/constants.py
index 590cba1..4dd6c55 100755
--- a/source/bin/nvdct/lib/constants.py
+++ b/source/bin/nvdct/lib/constants.py
@@ -7,18 +7,35 @@
 # Date  : 2024-12-11
 # File  : nvdct/lib/constants.py
 
-
-from dataclasses import dataclass
 from enum import Enum, unique, auto
-from logging import getLogger
+from logging import Logger, getLogger
 from os import environ
 from typing import Final
 
 #
-NVDCT_VERSION: Final[str] = '0.9.6-20241222'
+NVDCT_VERSION: Final[str] = '0.9.7-20241230'
+#
+OMD_ROOT: Final[str] = environ["OMD_ROOT"]
 #
+API_PORT: Final[int] = 5001
+CACHE_INTERFACES_DATA: Final[str] = 'interface_data'
+CMK_SITE_CONF: Final[str] = f'{OMD_ROOT}/etc/omd/site.conf'
+LOGGER: Logger = getLogger('root)')
+LOG_FILE: Final[str] = f'{OMD_ROOT}/var/log/nvdct.log'
+SCRIPT: Final[str] = '~/local/bin/nvdct/nvdct.py'
+TIME_FORMAT: Final[str] = '%Y-%m-%dT%H:%M:%S.%m'
+TIME_FORMAT_ARGPARSER: Final[str] = '%%Y-%%m-%%dT%%H:%%M:%%S.%%m'
+USER_DATA_FILE: Final[str] = 'nvdct.toml'
+DATAPATH: Final[str] = f'{OMD_ROOT}/var/check_mk/topology/data'
+
+
+class MyEnum(Enum):
+    def __get__(self, instance, owner):
+        return self.value
+
+
 @unique
-class ExitCodes(Enum):
+class ExitCodes(MyEnum):
     OK = 0
     BAD_OPTION_LIST = auto()
     BAD_TOML_FORMAT = auto()
@@ -26,104 +43,218 @@ class ExitCodes(Enum):
     AUTOMATION_SECRET_NOT_FOUND = auto()
     NO_LAYER_CONFIGURED = auto()
 
-    def __get__(self, instance, owner):
-        return self.value
 
 @unique
-class IPVersion(Enum):
+class IPVersion(MyEnum):
     IPv4 = 4
     IPv6 = 6
 
-    def __get__(self, instance, owner):
-        return self.value
 
-@dataclass(frozen=True)
-class Layer:
-    path: str
-    columns: str
-    label: str
-    host_label: str
+@unique
+class URLs(MyEnum):
+    NVDCT: Final[str] = 'https://thl-cmk.hopto.org/gitlab/checkmk/vendor-independent/nvdct'
+    # CDP: Final[str] = 'https://thl-cmk.hopto.org/gitlab/checkmk/vendor-independent/inventory/inv_cdp_cache'
+    # LLDP: Final[str] = 'LLDP: https://thl-cmk.hopto.org/gitlab/checkmk/vendor-independent/inventory/inv_lldp_cach'
+    # SNMP_IP_ADDRESS: Final[str] = 'https://thl-cmk.hopto.org/gitlab/checkmk/vendor-independent/inventory/inv_ip_address'
+    # LINUX_SNM_APPRESS: Final[str] = 'https://thl-cmk.hopto.org/gitlab/checkmk/vendor-independent/inventory/inv_lnx_if_ip'
+    # WINDOWS_IP_ADDRESS: Final[str] = 'https://thl-cmk.hopto.org/gitlab/checkmk/vendor-independent/inventory/inv_win_if_ip'
+    TOPIC_NV: Final[str] = 'https://thl-cmk.hopto.org/gitlab/explore/projects/topics/Network%20Visualization'
+    FORUM_SCHNETZ: Final[str] = 'https://forum.checkmk.com/t/network-visualization/41680'
 
-#
-OMD_ROOT: Final[str] = environ["OMD_ROOT"]
-#
-API_PORT: Final[int] = 5001
-CACHE_INTERFACES_DATA: Final[str] = 'interface_data'
-CMK_SITE_CONF: Final[str] = f'{OMD_ROOT}/etc/omd/site.conf'
-COLUMNS_CDP: Final[str] = 'neighbour_name,local_port,neighbour_port'
-COLUMNS_L3: Final[str] = 'address,device,cidr,network,type'
-COLUMNS_LLDP: Final[str] = 'neighbour_name,local_port,neighbour_port'
-DATAPATH: Final[str] = f'{OMD_ROOT}/var/check_mk/topology/data'
-HOME_URL: Final[str] = 'https://thl-cmk.hopto.org/gitlab/checkmk/vendor-independent/nvdct'
-HOST_LABEL_CDP: Final[str] = "'nvdct/has_cdp_neighbours' 'yes'"
-HOST_LABEL_L3V4_HOSTS: Final[str] = "'nvdct/l3v4_topology' 'host'"
-HOST_LABEL_L3V4_ROUTER: Final[str] = "'nvdct/l3v4_topology' 'router'"
-HOST_LABEL_L3V6_HOSTS: Final[str] = "'nvdct/l3v6_topology' 'host'"
-HOST_LABEL_L3V6_ROUTER: Final[str] = "'nvdct/l3v6_topology' 'router'"
-HOST_LABEL_LLDP: Final[str] = "'nvdct/has_lldp_neighbours' 'yes'"
-LABEL_CDP: Final[str] = 'CDP'
-LABEL_L3v4: Final[str] = 'L3v4'
-LABEL_L3v6: Final[str] = 'L3v6'
-LABEL_LLDP: Final[str] = 'LLDP'
-LABEL_STATIC: Final[str] = 'STATIC'
-LOGGER: Final[str] = getLogger('root)')
-LOG_FILE: Final[str] = f'{OMD_ROOT}/var/log/nvdct.log'
-MIN_CDP_VERSION: Final[str] = '0.7.1-20240320'
-MIN_LINUX_IP_ADDRESSES: Final[str] = '0.0.4-20241210'
-MIN_SNMP_IP_ADDRESSES: Final[str] = '0.0.6-20241210'
-MIN_WINDOWS_IP_ADDRESSES: Final[str] = '0.0.3-20241210'
-MIN_LLDP_VERSION: Final[str] = '0.9.3-20240320'
-PATH_CDP: Final[str] = 'networking,cdp_cache,neighbours'
-PATH_INTERFACES: Final[str] = 'networking,interfaces'
-PATH_L3: Final[str] = 'networking,addresses'
-PATH_LLDP: Final[str] = 'networking,lldp_cache,neighbours'
-SCRIPT: Final[str] = '~/local/bin/nvdct/nvdct.py'
-TIME_FORMAT: Final[str] = '%Y-%m-%dT%H:%M:%S.%m'
-TIME_FORMAT_ARGPARSER: Final[str] = '%%Y-%%m-%%dT%%H:%%M:%%S.%%m'
-USER_DATA_FILE: Final[str] = 'nvdct.toml'
-#
-TOML_CUSTOMERS : Final[str] = 'CUSTOMERS'
-TOML_CUSTOM_LAYERS : Final[str] = 'CUSTOM_LAYERS'
-TOML_EMBLEMS : Final[str] = 'EMBLEMS'
-TOML_L2_DROP_HOSTS: Final[str] = 'L2_DROP_HOSTS'
-TOML_L2_HOST_MAP : Final[str] = 'L2_HOST_MAP'
-TOML_L2_NEIGHBOUR_REPLACE_REGEX : Final[str] = 'L2_NEIGHBOUR_REPLACE_REGEX'
-TOML_L2_SEED_DEVICES: Final[str] = 'L2_SEED_DEVICES'
-TOML_L3V4_IGNORE_WILDCARD : Final[str] = 'L3V4_IGNORE_WILDCARD'
-TOML_L3_IGNORE_HOSTS : Final[str] = 'L3_IGNORE_HOSTS'
-TOML_L3_IGNORE_IP : Final[str] = 'L3_IGNORE_IP'
-TOML_L3_REPLACE : Final[str] = 'L3_REPLACE'
-TOML_L3_SUMMARIZE : Final[str] = 'L3_SUMMARIZE'
-TOML_MAP_SPEED_TO_THICKNESS : Final[str] = 'MAP_SPEED_TO_THICKNESS'
-TOML_PROTECTED_TOPOLOGIES : Final[str] = 'PROTECTED_TOPOLOGIES'
-TOML_SETTINGS : Final[str] = 'SETTINGS'
-TOML_SITES : Final[str] = 'SITES'
-TOML_STATIC_CONNECTIONS : Final[str] = 'STATIC_CONNECTIONS'
-#
-LAYERS = {
-    'CDP': Layer(
-        path=PATH_CDP,
-        columns=COLUMNS_CDP,
-        label=LABEL_CDP,
-        host_label=HOST_LABEL_CDP,
-    ),
-    'LLDP': Layer(
-        path=PATH_LLDP,
-        columns=COLUMNS_LLDP,
-        label=LABEL_LLDP,
-        host_label=HOST_LABEL_LLDP,
-    ),
-    'L3v4': Layer(
-        path=PATH_L3,
-        columns='',
-        label=LABEL_L3v4,
-        host_label=HOST_LABEL_L3V4_ROUTER,
-    ),
-    'L3v6': Layer(
-        path=PATH_L3,
-        columns='',
-        label=LABEL_L3v6,
-        host_label=HOST_LABEL_L3V6_ROUTER,
-    ),
-}
+
+@unique
+class Backends(MyEnum):
+    LIVESTATUS: Final[str] = 'LIVESTATUS'
+    MULTISITE: Final[str] = 'MULTISITE'
+    RESTAPI: Final[str] = 'RESTAPI'
+
+
+@unique
+class Case(MyEnum):
+    LOWER: Final[str] = 'LOWER'
+    UPPER: Final[str] = 'UPPER'
+
+@unique
+class CacheItems(MyEnum):
+    inventory = 'inventory'
+    interfaces = 'interfaces'
+
+@unique
+class CliLong(MyEnum):
+    ADJUST_TOML: Final[str] = '--adjust-toml'
+    API_PORT: Final[str] = '--api-port'
+    BACKEND: Final[str] = '--backend'
+    CASE: Final[str] = '--case'
+    CHECK_USER_DATA_ONLY: Final[str] = '--check-user-data-only'
+    DEFAULT: Final[str] = '--default'
+    DISPLAY_L2_NEIGHBOURS: Final[str] = '--display-l2-neighbours'
+    DONT_COMPARE: Final[str] = '--dont-compare'
+    FILTER_CUSTOMERS: Final[str] = '--filter-customers'
+    FILTER_SITES: Final[str] = '--filter-sites'
+    INCLUDE_L3_HOSTS: Final[str] = '--include-l3-hosts'
+    INCLUDE_L3_LOOPBACK: Final[str] = '--include-l3-loopback'
+    KEEP: Final[str] = '--keep'
+    LAYERS: Final[str] = '--layers'
+    LOG_FILE: Final[str] = '--log-file'
+    LOG_LEVEL: Final[str] = '--log-level'
+    LOG_TO_STDOUT: Final[str] = '--log-to-stdout'
+    MIN_AGE: Final[str] = '--min-age'
+    OUTPUT_DIRECTORY: Final[str] = '--output-directory'
+    PREFIX: Final[str] = '--prefix'
+    PRE_FETCH: Final[str] = '--pre-fetch'
+    QUIET: Final[str] = '--quiet'
+    REMOVE_DOMAIN: Final[str] = '--remove-domain'
+    SEED_DEVICES: Final[str] = '--seed-devices'
+    SKIP_L3_CIDR_0: Final[str] = '--skip-l3-cidr-0'
+    SKIP_L3_CIDR_32_128: Final[str] = '--skip-l3-cidr-32-128'
+    SKIP_L3_IF: Final[str] = '--skip-l3-if'
+    SKIP_L3_IP: Final[str] = '--skip-l3-ip'
+    SKIP_L3_PUBLIC: Final[str] = '--skip-l3-public'
+    TIME_FORMAT: Final[str] = '--time-format'
+    USER_DATA_FILE: Final[str] = '--user-data-file'
+    VERSION: Final[str] = '--version'
+
+
+@unique
+class EmblemNames(MyEnum):
+    HOST_NODE: Final[str] = 'host_node'
+    IP_ADDRESS: Final[str] = 'ip_address'
+    IP_NETWORK: Final[str] = 'ip_network'
+    L3_REPLACE: Final[str] = 'l3_replace'
+    L3_SUMMARIZE: Final[str] = 'l3_summarize'
+    SERVICE_NODE: Final[str] = 'service_node'
+
+
+@unique
+class EmblemValues(MyEnum):
+    ICON_AGGREGATION: Final[str] = 'icon_aggr'
+    ICON_ALERT_UNREACHABLE: Final[str] = 'icon_alert_unreach'
+    ICON_PLUGINS_CLOUD: Final[str] = 'icon_plugins_cloud'
+    IP_ADDRESS_80: Final[str] = 'ip-address_80'
+    IP_NETWORK_80: Final[str] = 'ip-network_80'
+
+
+@unique
+class HostLabels(MyEnum):
+    CDP: Final[str] = "'nvdct/has_cdp_neighbours' 'yes'"
+    L3V4_HOSTS: Final[str] = "'nvdct/l3v4_topology' 'host'"
+    L3V4_ROUTER: Final[str] = "'nvdct/l3v4_topology' 'router'"
+    L3V6_HOSTS: Final[str] = "'nvdct/l3v6_topology' 'host'"
+    L3V6_ROUTER: Final[str] = "'nvdct/l3v6_topology' 'router'"
+    LLDP: Final[str] = "'nvdct/has_lldp_neighbours' 'yes'"
+
+
+@unique
+class IncludeExclude(MyEnum):
+    INCLUDE: Final[str] = 'INCLUDE'
+    EXCLUDE: Final[str] = 'EXCLUDE'
+
+
+@unique
+class L2InvColumns(MyEnum):
+    NEIGHBOUR: Final[str] = 'neighbour_name'
+    LOCALPORT: Final[str] = 'local_port'
+    NEIGHBOURPORT: Final[str] = 'neighbour_port'
+
+
+@unique
+class L3InvColumns(MyEnum):
+    ADDRESS: Final[str] = 'address'
+    DEVICE: Final[str] = 'device'
+    CIDR: Final[str] = 'cidr'
+
+
+@unique
+class InvPaths(MyEnum):
+    CDP: Final[str] = 'networking,cdp_cache,neighbours'
+    INTERFACES: Final[str] = 'networking,interfaces'
+    L3: Final[str] = 'networking,addresses'
+    LLDP: Final[str] = 'networking,lldp_cache,neighbours'
+    LLDP_ATTRIBUTE: Final[str] = 'networking,lldp_cache'
+
+
+@unique
+class Layers(MyEnum):
+    CDP: Final[str] = 'CDP'
+    LLDP: Final[str] = 'LLDP'
+    L3V4: Final[str] = 'L3v4'
+    L3V6: Final[str] = 'L3v6'
+    STATIC: Final[str] = 'STATIC'
+
+
+@unique
+class LogLevels(MyEnum):
+    CRITICAL: Final[str] = 'CRITICAL'
+    FATAL: Final[str] = 'FATAL'
+    ERROR: Final[str] = 'ERROR'
+    WARNING: Final[str] = 'WARNING'
+    INFO: Final[str] = 'INFO'
+    DEBUG: Final[str] = 'DEBUG'
+    OFF: Final[str] = 'OFF'
+
+
+class MinVersions(MyEnum):
+    CDP: Final[str] = '0.7.1-20240320'
+    LLDP: Final[str] = '0.9.3-20240320'
+    LINUX_IP_ADDRESSES: Final[str] = '0.0.4-20241210'
+    SNMP_IP_ADDRESSES: Final[str] = '0.0.6-20241210'
+    WINDOWS_IP_ADDRESSES: Final[str] = '0.0.3-20241210'
+
+
+@unique
+class TomlSections(MyEnum):
+    CUSTOMERS: Final[str] = 'CUSTOMERS'
+    EMBLEMS: Final[str] = 'EMBLEMS'
+    L2_DROP_NEIGHBOURS: Final[str] = 'L2_DROP_NEIGHBOURS'
+    L2_HOST_MAP: Final[str] = 'L2_HOST_MAP'
+    L2_NEIGHBOUR_REPLACE_REGEX: Final[str] = 'L2_NEIGHBOUR_REPLACE_REGEX'
+    L2_SEED_DEVICES: Final[str] = 'L2_SEED_DEVICES'
+    L3V4_IGNORE_WILDCARD: Final[str] = 'L3V4_IGNORE_WILDCARD'
+    L3_IGNORE_HOSTS: Final[str] = 'L3_IGNORE_HOSTS'
+    L3_IGNORE_IP: Final[str] = 'L3_IGNORE_IP'
+    L3_REPLACE: Final[str] = 'L3_REPLACE'
+    L3_SUMMARIZE: Final[str] = 'L3_SUMMARIZE'
+    MAP_SPEED_TO_THICKNESS: Final[str] = 'MAP_SPEED_TO_THICKNESS'
+    PROTECTED_TOPOLOGIES: Final[str] = 'PROTECTED_TOPOLOGIES'
+    SETTINGS: Final[str] = 'SETTINGS'
+    SITES: Final[str] = 'SITES'
+    STATIC_CONNECTIONS: Final[str] = 'STATIC_CONNECTIONS'
+
+
+def cli_long_to_toml(cli_param: str) -> str:
+    return cli_param.strip('-').replace('-', '_')
+
+
+@unique
+class TomlSettings(MyEnum):
+    ADJUST_TOML: Final[str] = cli_long_to_toml(CliLong.ADJUST_TOML)
+    API_PORT: Final[str] = cli_long_to_toml(CliLong.API_PORT)
+    BACKEND: Final[str] = cli_long_to_toml(CliLong.BACKEND)
+    CASE: Final[str] = cli_long_to_toml(CliLong.CASE)
+    CHECK_USER_DATA_ONLY: Final[str] = cli_long_to_toml(CliLong.CHECK_USER_DATA_ONLY)
+    DEFAULT: Final[str] = cli_long_to_toml(CliLong.DEFAULT)
+    DISPLAY_L2_NEIGHBOURS: Final[str] = cli_long_to_toml(CliLong.DISPLAY_L2_NEIGHBOURS)
+    DONT_COMPARE: Final[str] = cli_long_to_toml(CliLong.DONT_COMPARE)
+    FILTER_CUSTOMERS: Final[str] = cli_long_to_toml(CliLong.FILTER_CUSTOMERS)
+    FILTER_SITES: Final[str] = cli_long_to_toml(CliLong.FILTER_SITES)
+    INCLUDE_L3_HOSTS: Final[str] = cli_long_to_toml(CliLong.INCLUDE_L3_HOSTS)
+    INCLUDE_L3_LOOPBACK: Final[str] = cli_long_to_toml(CliLong.INCLUDE_L3_LOOPBACK)
+    KEEP: Final[str] = cli_long_to_toml(CliLong.KEEP)
+    LAYERS: Final[str] = cli_long_to_toml(CliLong.LAYERS)
+    LOG_FILE: Final[str] = cli_long_to_toml(CliLong.LOG_FILE)
+    LOG_LEVEL: Final[str] = cli_long_to_toml(CliLong.LOG_LEVEL)
+    LOG_TO_STDOUT: Final[str] = cli_long_to_toml(CliLong.LOG_TO_STDOUT)
+    MIN_AGE: Final[str] = cli_long_to_toml(CliLong.MIN_AGE)
+    OUTPUT_DIRECTORY: Final[str] = cli_long_to_toml(CliLong.OUTPUT_DIRECTORY)
+    PREFIX: Final[str] = cli_long_to_toml(CliLong.PREFIX)
+    PRE_FETCH: Final[str] = cli_long_to_toml(CliLong.PRE_FETCH)
+    QUIET: Final[str] = cli_long_to_toml(CliLong.QUIET)
+    REMOVE_DOMAIN: Final[str] = cli_long_to_toml(CliLong.REMOVE_DOMAIN)
+    SKIP_L3_CIDR_0: Final[str] = cli_long_to_toml(CliLong.SKIP_L3_CIDR_0)
+    SKIP_L3_CIDR_32_128: Final[str] = cli_long_to_toml(CliLong.SKIP_L3_CIDR_32_128)
+    SKIP_L3_IF: Final[str] = cli_long_to_toml(CliLong.SKIP_L3_IF)
+    SKIP_L3_IP: Final[str] = cli_long_to_toml(CliLong.SKIP_L3_IP)
+    SKIP_L3_PUBLIC: Final[str] = cli_long_to_toml(CliLong.SKIP_L3_PUBLIC)
+    TIME_FORMAT: Final[str] = cli_long_to_toml(CliLong.TIME_FORMAT)
+    USER_DATA_FILE: Final[str] = cli_long_to_toml(CliLong.USER_DATA_FILE)
 
diff --git a/source/bin/nvdct/lib/settings.py b/source/bin/nvdct/lib/settings.py
index e625634..5b82bff 100755
--- a/source/bin/nvdct/lib/settings.py
+++ b/source/bin/nvdct/lib/settings.py
@@ -12,39 +12,29 @@
 
 from collections.abc import Mapping
 from ipaddress import AddressValueError, NetmaskValueError, ip_address, ip_network
-from logging import CRITICAL, FATAL, ERROR, WARNING, INFO, DEBUG
+from logging import CRITICAL, DEBUG, ERROR, FATAL, INFO, WARNING
+from pathlib import Path
 from sys import exit as sys_exit
 from time import strftime
 from typing import Dict, List, NamedTuple, Tuple
-from pathlib import Path
 
 from lib.constants import (
     API_PORT,
+    Backends,
+    Case,
+    EmblemValues,
+    EmblemNames,
     ExitCodes,
+    IncludeExclude,
     LOGGER,
     LOG_FILE,
-    Layer,
+    LogLevels,
     OMD_ROOT,
     TIME_FORMAT,
+    TomlSections,
+    TomlSettings,
     USER_DATA_FILE,
 
-    TOML_CUSTOMERS,
-    TOML_CUSTOM_LAYERS,
-    TOML_EMBLEMS,
-    TOML_L2_DROP_HOSTS,
-    TOML_L2_HOST_MAP,
-    TOML_L2_NEIGHBOUR_REPLACE_REGEX,
-    TOML_L2_SEED_DEVICES,
-    TOML_L3V4_IGNORE_WILDCARD,
-    TOML_L3_IGNORE_HOSTS,
-    TOML_L3_IGNORE_IP,
-    TOML_L3_REPLACE,
-    TOML_L3_SUMMARIZE,
-    TOML_MAP_SPEED_TO_THICKNESS,
-    TOML_PROTECTED_TOPOLOGIES,
-    TOML_SETTINGS,
-    TOML_SITES,
-    TOML_STATIC_CONNECTIONS,
 )
 from lib.utils import (
     get_data_from_toml,
@@ -72,18 +62,18 @@ class Thickness(NamedTuple):
 
 
 class StaticConnection(NamedTuple):
+    left_host: str
+    left_service: str
     right_host: str
     right_service: str
-    left_service: str
-    left_host: str
 
 
 class Wildcard(NamedTuple):
+    bit_pattern: int
     int_ip_address: int
     int_wildcard: int
     ip_address: str
     wildcard: str
-    bit_pattern: int
 
 
 class Settings:
@@ -93,30 +83,36 @@ class Settings:
     ):
         # cli defaults
         self.__settings = {
-            # 'api_port': 80,
-            'backend': 'MULTISITE',
-            'case': None,
-            'check_user_data_only': False,
-            'default': False,
-            'dont_compare': False,
-            'filter_customers': None,
-            'filter_sites': None,
-            'include_l3_hosts': False,
-            'keep': False,
-            'remove_domain': False,
-            'layers': [],
-            'log_file': LOG_FILE,
-            'log_level': 'WARNING',
-            'log_to_stdout': False,
-            'min_age': 0,
-            'output_directory': None,
-            'prefix': None,
-            'quiet': False,
-            'pre_fetch': False,
-            'skip_l3_if': False,
-            'skip_l3_ip': False,
-            'time_format': TIME_FORMAT,
-            'user_data_file': f'{OMD_ROOT}/local/bin/nvdct/conf/{USER_DATA_FILE}',
+            TomlSettings.ADJUST_TOML: False,
+            TomlSettings.API_PORT: None,
+            TomlSettings.BACKEND: Backends.MULTISITE,
+            TomlSettings.CASE: None,
+            TomlSettings.CHECK_USER_DATA_ONLY: False,
+            TomlSettings.DEFAULT: False,
+            TomlSettings.DISPLAY_L2_NEIGHBOURS: False,
+            TomlSettings.DONT_COMPARE: False,
+            TomlSettings.FILTER_CUSTOMERS: None,
+            TomlSettings.FILTER_SITES: None,
+            TomlSettings.INCLUDE_L3_HOSTS: False,
+            TomlSettings.INCLUDE_L3_LOOPBACK: False,
+            TomlSettings.KEEP: False,
+            TomlSettings.LAYERS: [],
+            TomlSettings.LOG_FILE: LOG_FILE,
+            TomlSettings.LOG_LEVEL: LogLevels.WARNING,
+            TomlSettings.LOG_TO_STDOUT: False,
+            TomlSettings.MIN_AGE: 0,
+            TomlSettings.OUTPUT_DIRECTORY: None,
+            TomlSettings.PREFIX: None,
+            TomlSettings.PRE_FETCH: False,
+            TomlSettings.QUIET: False,
+            TomlSettings.REMOVE_DOMAIN: False,
+            TomlSettings.SKIP_L3_CIDR_0: False,
+            TomlSettings.SKIP_L3_CIDR_32_128: False,
+            TomlSettings.SKIP_L3_IF: False,
+            TomlSettings.SKIP_L3_IP: False,
+            TomlSettings.SKIP_L3_PUBLIC: False,
+            TomlSettings.TIME_FORMAT: TIME_FORMAT,
+            TomlSettings.USER_DATA_FILE: f'{OMD_ROOT}/local/bin/nvdct/conf/{USER_DATA_FILE}',
         }
         # args in the form {'s, __seed_devices': 'CORE01', 'p, __path_in_inventory': None, ... }}
         # we will remove 's, __'
@@ -125,16 +121,16 @@ class Settings:
         )
 
         self.__user_data = get_data_from_toml(
-            file=self.__args.get('user_data_file', self.user_data_file)
+            file=self.__args.get(TomlSettings.USER_DATA_FILE, self.user_data_file)
         )
 
-        if self.__args.get('check_user_data_only'):
+        if self.__args.get(TomlSettings.CHECK_USER_DATA_ONLY):
             LOGGER.info(msg=f'Could read/parse the user data from {self.user_data_file}')
             print(f'Could read/parse the user data from {self.user_data_file}')
             sys_exit(ExitCodes.OK)
 
         # defaults -> overridden by toml -> overridden by cli
-        self.__settings.update(self.__user_data.get(TOML_SETTINGS, {}))
+        self.__settings.update(self.__user_data.get(TomlSections.SETTINGS, {}))
         self.__settings.update(self.__args)
 
         if self.layers:
@@ -149,18 +145,17 @@ class Settings:
         self.__api_port: int | None = None
 
         # init user data with defaults
-        self.__custom_layers: List[StaticConnection] | None = None
         self.__customers: List[str] | None = None
         self.__emblems: Emblems | None = None
-        self.__l2_drop_host: List[str] | None = None
+        self.__l2_drop_neighbours: List[str] | None = None
         self.__l2_host_map: Dict[str, str] | None = None
         self.__l2_neighbour_replace_regex: List[Tuple[str, str]] | None = None
         self.__l2_seed_devices: List[str] | None = None
         self.__l3_ignore_hosts: List[str] | None = None
         self.__l3_ignore_ip: List[ip_network] | None = None
-        self.__l3v4_ignore_wildcard: List[Wildcard] | None = None
         self.__l3_replace: Dict[str, str] | None = None
         self.__l3_summarize: List[ip_network] | None = None
+        self.__l3v4_ignore_wildcard: List[Wildcard] | None = None
         self.__map_speed_to_thickness: List[Thickness] | None = None
         self.__protected_topologies: List[str] | None = None
         self.__sites: List[str] | None = None
@@ -172,92 +167,108 @@ class Settings:
     @property  # --api-port
     def api_port(self) -> int:
         if self.__api_port is None:
-            if self.__settings.get('api_port'):
-                self.__api_port = int(self.__settings.get('api_port'))
+            if self.__settings.get(TomlSettings.API_PORT):
+                self.__api_port = int(self.__settings.get(TomlSettings.API_PORT))
             else:
                 self.__api_port = get_local_cmk_api_port()
             if self.__api_port is None:
-                self.__api_port = API_PORT
+               self.__api_port = API_PORT
 
         return self.__api_port
 
     @property  # -b --backend
     def backend(self) -> str:
-        if str(self.__settings['backend']) in ['LIVESTATUS', 'MULTISITE', 'RESTAPI']:
-            return str(self.__settings['backend'])
+        if str(self.__settings[TomlSettings.BACKEND]) in [
+            Backends.LIVESTATUS,
+            Backends.MULTISITE,
+            Backends.RESTAPI
+        ]:
+            return str(self.__settings[TomlSettings.BACKEND])
         else:  # fallback to defaukt -> exit ??
             LOGGER.warning(
-                f'Unknown backend: {self.__settings["backend"]}. Accepted backends are: '
-                'LIVESTATUS, MULTISITE, RESTAPI. Fall back zo MULTISITE.'
+                f'Unknown backend: {self.__settings[TomlSettings.BACKEND]}. Accepted backends are: '
+                f'{Backends.LIVESTATUS}, {Backends.MULTISITE}, {Backends.RESTAPI}. Fall back to {Backends.MULTISITE}.'
             )
-            return 'MULTISITE'
+            return Backends.MULTISITE
 
     @property  # --case
     def case(self) -> str | None:
-        if self.__settings['case'] in ['LOWER', 'UPPER']:
-            return self.__settings['case']
-        elif self.__settings['case'] is not None:
+        if self.__settings[TomlSettings.CASE] in [Case.LOWER, Case.UPPER]:
+            return self.__settings[TomlSettings.CASE]
+        elif self.__settings[TomlSettings.CASE] is not None:
             LOGGER.warning(
-                    f'Unknon case setting {self.__settings["case"]}. '
-                    'Accepted are LOWER|UPPER. Fallback to no change.'
+                    f'Unknown case setting {self.__settings[TomlSettings.CASE]}. '
+                    f'Accepted are {Case.LOWER}|{Case.UPPER}. Fallback to no change.'
                 )
         return None
 
     @property  # --check-user-data-only
     def check_user_data_only(self) -> bool:
-        return bool(self.__settings['check_user_data_only'])
+        return bool(self.__settings[TomlSettings.CHECK_USER_DATA_ONLY])
 
     @property  # -d --default
     def default(self) -> bool:
-        return bool(self.__settings['default'])
+        return bool(self.__settings[TomlSettings.DEFAULT])
+
+    @property  # --display-l2-neighbours
+    def display_l2_neighbours(self) -> bool:
+        return bool(self.__settings[TomlSettings.DISPLAY_L2_NEIGHBOURS])
 
     @property  # --dont-compare
     def dont_compare(self) -> bool:
-        return bool(self.__settings['dont_compare'])
+        return bool(self.__settings[TomlSettings.DONT_COMPARE])
 
     @property  # --filter-customers
     def filter_customers(self) -> str | None:
-        if self.__settings['filter_customers'] in ['INCLUDE', 'EXCLUDE']:
-            return self.__settings['filter_customers']
-        elif self.__settings['filter_customers'] is not None:
+        if self.__settings[TomlSettings.FILTER_CUSTOMERS] in [IncludeExclude.INCLUDE, IncludeExclude.EXCLUDE]:
+            return self.__settings[TomlSettings.FILTER_CUSTOMERS]
+        elif self.__settings[TomlSettings.FILTER_CUSTOMERS] is not None:
             LOGGER.error(
-                f'Wrong setting for "filter_customers": '
-                f'{self.__settings["filter_customers"]}, supported settings INCLUDE|EXCLUDE.'
+                f'Wrong setting for "{TomlSettings.FILTER_CUSTOMERS}": '
+                f'{self.__settings[TomlSettings.FILTER_CUSTOMERS]}, supported settings {IncludeExclude.INCLUDE}|{IncludeExclude.EXCLUDE}.'
             )
         return None
 
     @property  # --filter-sites
     def filter_sites(self) -> str | None:
-        if self.__settings['filter_sites'] in ['INCLUDE', 'EXCLUDE']:
-            return self.__settings['filter_sites']
-        elif self.__settings['filter_sites'] is not None:
+        if self.__settings[TomlSettings.FILTER_SITES] in [IncludeExclude.INCLUDE, IncludeExclude.EXCLUDE]:
+            return self.__settings[TomlSettings.FILTER_SITES]
+        elif self.__settings[TomlSettings.FILTER_SITES] is not None:
             LOGGER.error(
-                f'Wrong setting for "filter_sites": '
-                f'{self.__settings["filter_sites"]}, supported settings INCLUDE|EXCLUDE.'
+                f'Wrong setting for "{TomlSettings.FILTER_SITES}": '
+                f'{self.__settings[TomlSettings.FILTER_SITES]}, supported settings {IncludeExclude.INCLUDE}|{IncludeExclude.EXCLUDE}.'
             )
         return None
 
+    @property  # --include-l3-hosts
+    def fix_toml(self) -> bool:
+        return bool(self.__settings[TomlSettings.ADJUST_TOML])
+
     @property  # --include-l3-hosts
     def include_l3_hosts(self) -> bool:
-        return bool(self.__settings['include_l3_hosts'])
+        return bool(self.__settings[TomlSettings.INCLUDE_L3_HOSTS])
+
+    @property  # --skip-l3-ip
+    def include_l3_loopback(self) -> bool:
+        return bool(self.__settings[TomlSettings.INCLUDE_L3_LOOPBACK])
 
     @property  # --keep
     def keep(self) -> int | None:
-        if isinstance(self.__settings['keep'], int):
-            return max(self.__settings['keep'], 0)
+        if isinstance(self.__settings[TomlSettings.KEEP], int):
+            return max(self.__settings[TomlSettings.KEEP], 0)
         return None
 
     @property  # --keep-domain
     def remove_domain(self) -> bool:
-        return bool(self.__settings['remove_domain'])
+        return bool(self.__settings[TomlSettings.REMOVE_DOMAIN])
 
     @property  # --layers
     def layers(self) -> List[str]:
-        return self.__settings['layers']
+        return self.__settings[TomlSettings.LAYERS]
 
     @property  # --log-file
     def log_file(self) -> str:
-        raw_log_file = str(Path(str(self.__settings['log_file'])).expanduser())
+        raw_log_file = str(Path(str(self.__settings[TomlSettings.LOG_FILE])).expanduser())
         if is_valid_log_file(raw_log_file):
             return raw_log_file
         else:
@@ -267,67 +278,79 @@ class Settings:
     @property  # --log-level
     def loglevel(self) -> int:
         log_levels = {
-            'DEBUG': DEBUG,
-            'INFO': INFO,
-            'WARNING': WARNING,
-            'ERROR': ERROR,
-            'FATAL': FATAL,
-            'CRITICAL': CRITICAL,
-            'OFF': -1,
+            LogLevels.DEBUG: DEBUG,
+            LogLevels.INFO: INFO,
+            LogLevels.WARNING: WARNING,
+            LogLevels.ERROR: ERROR,
+            LogLevels.FATAL: FATAL,
+            LogLevels.CRITICAL: CRITICAL,
+            LogLevels.OFF: -1,
         }
-        return log_levels.get(self.__settings['log_level'], WARNING)
+        return log_levels.get(self.__settings[TomlSettings.LOG_LEVEL], WARNING)
 
     @property  # --log-to-stdout
     def log_to_stdtout(self) -> bool:
-        return bool(self.__settings['log_to_stdout'])
+        return bool(self.__settings[TomlSettings.LOG_TO_STDOUT])
 
     @property  # --min-age
     def min_age(self) -> int:
-        if isinstance(self.__settings['min_age'], int):
-            return max(self.__settings['min_age'], 0)
+        if isinstance(self.__settings[TomlSettings.MIN_AGE], int):
+            return max(self.__settings[TomlSettings.MIN_AGE], 0)
         else:
             return 0
 
     @property  # --output-directory
     def output_directory(self) -> str:
         # init output directory with current time if not set
-        if not self.__settings['output_directory']:
-            self.__settings['output_directory'] = f'{strftime(self.__settings["time_format"])}'
-        if is_valid_output_directory(str(self.__settings['output_directory'])):
-            return str(self.__settings['output_directory'])
+        if not self.__settings[TomlSettings.OUTPUT_DIRECTORY]:
+            self.__settings[TomlSettings.OUTPUT_DIRECTORY] = f'{strftime(self.__settings[TomlSettings.TIME_FORMAT])}'
+        if is_valid_output_directory(str(self.__settings[TomlSettings.OUTPUT_DIRECTORY])):
+            return str(self.__settings[TomlSettings.OUTPUT_DIRECTORY])
         else:
             LOGGER.error('Falling back to "nvdct"')
             return 'nvdct'
 
     @property  # --prefix
     def prefix(self) -> str | None:
-        if self.__settings['prefix'] is not None:
-            return str(self.__settings['prefix'])
+        if self.__settings[TomlSettings.PREFIX] is not None:
+            return str(self.__settings[TomlSettings.PREFIX])
         return None
 
     @property  # --pre-fill-cache
     def pre_fetch(self) -> bool:
-        return bool(self.__settings['pre_fetch'])
+        return bool(self.__settings[TomlSettings.PRE_FETCH])
 
     @property  # --quiet
     def quiet(self) -> bool:
-        return bool(self.__settings['quiet'])
+        return bool(self.__settings[TomlSettings.QUIET])
+
+    @property  # --skip-l3-cidr-0
+    def skip_l3_cidr_0(self) -> bool:
+        return bool(self.__settings[TomlSettings.SKIP_L3_CIDR_0])
+
+    @property  # --skip-l3-cidr-32-128
+    def skip_l3_cidr_32_128(self) -> bool:
+        return bool(self.__settings[TomlSettings.SKIP_L3_CIDR_32_128])
 
     @property  # --skip-l3-if
     def skip_l3_if(self) -> bool:
-        return bool(self.__settings['skip_l3_if'])
+        return bool(self.__settings[TomlSettings.SKIP_L3_IF])
 
     @property  # --skip-l3-ip
     def skip_l3_ip(self) -> bool:
-        return bool(self.__settings['skip_l3_ip'])
+        return bool(self.__settings[TomlSettings.SKIP_L3_IP])
+
+    @property  # --skip-l3-public
+    def skip_l3_public(self) -> bool:
+        return bool(self.__settings[TomlSettings.SKIP_L3_PUBLIC])
 
     @property  # --time-format
     def time_format(self) -> str:
-        return str(self.__settings['time_format'])
+        return str(self.__settings[TomlSettings.TIME_FORMAT])
 
     @property  # --user-data-file
     def user_data_file(self) -> str:
-        return str(self.__settings['user_data_file'])
+        return str(self.__settings[TomlSettings.USER_DATA_FILE])
 
     #
     #  user data setting
@@ -336,59 +359,36 @@ class Settings:
     def customers(self) -> List[str]:
         if self.__customers is None:
             self.__customers = [
-                str(customer) for customer in set(self.__user_data.get(TOML_CUSTOMERS, []))
+                str(customer) for customer in set(self.__user_data.get(TomlSections.CUSTOMERS, []))
                 if is_valid_customer_name(customer)]
             LOGGER.info(f'Found {len(self.__customers)} to filter on')
         return self.__customers
 
-    @property
-    def custom_layers(self) -> List[Layer]:
-        if self.__custom_layers is None:
-            self.__custom_layers = []
-            for _layer in self.__user_data.get(TOML_CUSTOM_LAYERS, []):
-                try:
-                    self.__custom_layers.append(Layer(
-                        path=_layer['path'],
-                        columns=_layer['columns'],
-                        label=_layer['label'],
-                        host_label=_layer['host_label']
-                    ))
-                except KeyError:
-                    LOGGER.error(
-                        f'Invalid entry in {TOML_CUSTOM_LAYERS} -> {_layer} -> ignored'
-                    )
-                    continue
-            LOGGER.critical(
-                f'Valid entries in {TOML_CUSTOM_LAYERS} found: {len(self.__custom_layers)}/'
-                f'{len(self.__user_data.get(TOML_CUSTOM_LAYERS, []))}'
-            )
-        return self.__custom_layers
-
     @property
     def emblems(self) -> Emblems:
         if self.__emblems is None:
-            raw_emblems = self.__user_data.get(TOML_EMBLEMS, {})
+            raw_emblems = self.__user_data.get(TomlSections.EMBLEMS, {})
             self.__emblems = Emblems(
-                host_node=str(raw_emblems.get('host_node', 'icon_missing')),
-                ip_address=str(raw_emblems.get('ip_address', 'ip-address_80')),
-                ip_network=str(raw_emblems.get('ip_network', 'ip-network_80')),
-                l3_replace=str(raw_emblems.get('l3_replace', 'icon_plugins_cloud')),
-                l3_summarize=str(raw_emblems.get('l3_summarize', 'icon_aggr')),
-                service_node=str(raw_emblems.get('service_node', 'icon_missing')),
+                host_node=str(raw_emblems.get(EmblemNames.HOST_NODE, EmblemValues.ICON_ALERT_UNREACHABLE)),
+                ip_address=str(raw_emblems.get(EmblemNames.IP_ADDRESS, EmblemValues.IP_ADDRESS_80)),
+                ip_network=str(raw_emblems.get(EmblemNames.IP_NETWORK, EmblemValues.IP_NETWORK_80)),
+                l3_replace=str(raw_emblems.get(EmblemNames.L3_REPLACE, EmblemValues.ICON_PLUGINS_CLOUD)),
+                l3_summarize=str(raw_emblems.get(EmblemNames.L3_SUMMARIZE, EmblemValues.ICON_AGGREGATION)),
+                service_node=str(raw_emblems.get(EmblemNames.SERVICE_NODE, EmblemValues.ICON_ALERT_UNREACHABLE)),
             )
         return self.__emblems
 
     @property
-    def l2_drop_hosts(self) -> List[str]:
-        if self.__l2_drop_host is None:
-            self.__l2_drop_host = [str(host) for host in set(self.__user_data.get(TOML_L2_DROP_HOSTS, []))]
-        return self.__l2_drop_host
+    def l2_drop_neighbours(self) -> List[str]:
+        if self.__l2_drop_neighbours is None:
+            self.__l2_drop_neighbours = [str(host) for host in set(self.__user_data.get(TomlSections.L2_DROP_NEIGHBOURS, []))]
+        return self.__l2_drop_neighbours
 
     @property
     def l2_seed_devices(self) -> List[str]:
         if self.__l2_seed_devices is None:
             self.__l2_seed_devices = list(set(str(host) for host in (
-                self.__user_data.get(TOML_L2_SEED_DEVICES, [])) if is_valid_hostname(host)))
+                self.__user_data.get(TomlSections.L2_SEED_DEVICES, [])) if is_valid_hostname(host)))
         return self.__l2_seed_devices
 
     @property
@@ -396,7 +396,7 @@ class Settings:
         if self.__l2_host_map is None:
             self.__l2_host_map = {
                 str(host): str(replace_host) for host, replace_host in self.__user_data.get(
-                    TOML_L2_HOST_MAP, {}
+                    TomlSections.L2_HOST_MAP, {}
                 ).items() if is_valid_hostname(host)
             }
         return self.__l2_host_map
@@ -407,7 +407,7 @@ class Settings:
             self.__l2_neighbour_replace_regex = [
                 (
                     str(regex), str(replace)
-                ) for regex, replace in self.__user_data.get(TOML_L2_NEIGHBOUR_REPLACE_REGEX, {}).items()
+                ) for regex, replace in self.__user_data.get(TomlSections.L2_NEIGHBOUR_REPLACE_REGEX, {}).items()
             ]
         return self.__l2_neighbour_replace_regex
 
@@ -415,7 +415,7 @@ class Settings:
     def l3_ignore_hosts(self) -> List[str]:
         if self.__l3_ignore_hosts is None:
             self.__l3_ignore_hosts = [str(host) for host in set(self.__user_data.get(
-                TOML_L3_IGNORE_HOSTS, []
+                TomlSections.L3_IGNORE_HOSTS, []
             )) if is_valid_hostname(host)]
         return self.__l3_ignore_hosts
 
@@ -423,31 +423,30 @@ class Settings:
     def l3_ignore_ips(self) -> List[ip_network]:
         if self.__l3_ignore_ip is None:
             self.__l3_ignore_ip = []
-            for raw_ip_network in self.__user_data.get(TOML_L3_IGNORE_IP, []):
+            for raw_ip_network in self.__user_data.get(TomlSections.L3_IGNORE_IP, []):
                 try:
                     self.__l3_ignore_ip.append(ip_network(raw_ip_network, strict=False))
                 except (AddressValueError, NetmaskValueError):
                     LOGGER.error(
-                        f'Invalid entry in {TOML_L3_IGNORE_IP} found: {raw_ip_network} -> ignored'
+                        f'Invalid entry in {TomlSections.L3_IGNORE_IP} found: {raw_ip_network} -> ignored'
                     )
                     continue
             LOGGER.info(
-                f'Valid entries in {TOML_L3_IGNORE_IP} found: {len(self.__l3_ignore_ip)}/'
-                f'{len(self.__user_data.get(TOML_L3_IGNORE_IP, []))}'
+                f'Valid entries in {TomlSections.L3_IGNORE_IP} found: {len(self.__l3_ignore_ip)}/'
+                f'{len(self.__user_data.get(TomlSections.L3_IGNORE_IP, []))}'
             )
-
         return self.__l3_ignore_ip
 
     @property
     def l3v4_ignore_wildcard(self) -> List[Wildcard]:
         if self.__l3v4_ignore_wildcard is None:
             self.__l3v4_ignore_wildcard = []
-            for entry in self.__user_data.get(TOML_L3V4_IGNORE_WILDCARD, []):
+            for entry in self.__user_data.get(TomlSections.L3V4_IGNORE_WILDCARD, []):
                 try:
                     raw_ip_address, wildcard = entry
                 except ValueError:
                     LOGGER.error(
-                        f'Invalid entry in {TOML_L3V4_IGNORE_WILDCARD} -> {entry} -> ignored'
+                        f'Invalid entry in {TomlSections.L3V4_IGNORE_WILDCARD} -> {entry} -> ignored'
                     )
                     continue
                 try:
@@ -466,12 +465,12 @@ class Settings:
                     ))
                 except (AddressValueError, NetmaskValueError):
                     LOGGER.error(
-                        f'Invalid entry in {TOML_L3V4_IGNORE_WILDCARD} -> {entry} -> ignored'
+                        f'Invalid entry in {TomlSections.L3V4_IGNORE_WILDCARD} -> {entry} -> ignored'
                     )
                     continue
             LOGGER.info(
-                f'Valid entries in {TOML_L3V4_IGNORE_WILDCARD} found: {len(self.__l3v4_ignore_wildcard)}/'
-                f'{len(self.__user_data.get(TOML_L3V4_IGNORE_WILDCARD, []))}'
+                f'Valid entries in {TomlSections.L3V4_IGNORE_WILDCARD} found: {len(self.__l3v4_ignore_wildcard)}/'
+                f'{len(self.__user_data.get(TomlSections.L3V4_IGNORE_WILDCARD, []))}'
             )
         return self.__l3v4_ignore_wildcard
 
@@ -479,12 +478,12 @@ class Settings:
     def l3_replace(self) -> Dict[str, str]:
         if self.__l3_replace is None:
             self.__l3_replace = {}
-            for raw_ip_network, node in self.__user_data.get(TOML_L3_REPLACE, {}).items():
+            for raw_ip_network, node in self.__user_data.get(TomlSections.L3_REPLACE, {}).items():
                 try:
                     _ip_network = ip_network(raw_ip_network)  # noqa: F841
                 except (AddressValueError, NetmaskValueError):
                     LOGGER.error(
-                        f'Invalid entry in {TOML_L3_REPLACE} found: {raw_ip_network} -> line ignored'
+                        f'Invalid entry in {TomlSections.L3_REPLACE} found: {raw_ip_network} -> line ignored'
                     )
                     continue
                 if not is_valid_hostname(node):
@@ -492,8 +491,8 @@ class Settings:
                     continue
                 self.__l3_replace[raw_ip_network] = str(node)
             LOGGER.info(
-                f'Valid entries in {TOML_L3_REPLACE} found: {len(self.__l3_replace)}/'
-                f'{len(self.__user_data.get(TOML_L3_REPLACE, {}))}'
+                f'Valid entries in {TomlSections.L3_REPLACE} found: {len(self.__l3_replace)}/'
+                f'{len(self.__user_data.get(TomlSections.L3_REPLACE, {}))}'
             )
         return self.__l3_replace
 
@@ -501,17 +500,17 @@ class Settings:
     def l3_summarize(self) -> List[ip_network]:
         if self.__l3_summarize is None:
             self.__l3_summarize = []
-            for raw_ip_network in self.__user_data.get(TOML_L3_SUMMARIZE, []):
+            for raw_ip_network in self.__user_data.get(TomlSections.L3_SUMMARIZE, []):
                 try:
                     self.__l3_summarize.append(ip_network(raw_ip_network, strict=False))
                 except (AddressValueError, NetmaskValueError):
                     LOGGER.error(
-                        f'Invalid entry in {TOML_L3_SUMMARIZE} -> {raw_ip_network} -> ignored'
+                        f'Invalid entry in {TomlSections.L3_SUMMARIZE} -> {raw_ip_network} -> ignored'
                     )
                     continue
             LOGGER.info(
-                f'Valid entries in {TOML_L3_SUMMARIZE} found: {len(self.__l3_summarize)}/'
-                f'{len(self.__user_data.get(TOML_L3_SUMMARIZE, []))}'
+                f'Valid entries in {TomlSections.L3_SUMMARIZE} found: {len(self.__l3_summarize)}/'
+                f'{len(self.__user_data.get(TomlSections.L3_SUMMARIZE, []))}'
             )
         return self.__l3_summarize
 
@@ -520,7 +519,7 @@ class Settings:
         if self.__map_speed_to_thickness is None:
             self.__map_speed_to_thickness = []
             map_speed_to_thickness = self.__user_data.get(
-                TOML_MAP_SPEED_TO_THICKNESS, {}
+                TomlSections.MAP_SPEED_TO_THICKNESS, {}
             )
             for speed, thickness in map_speed_to_thickness.items():
                 try:
@@ -530,12 +529,12 @@ class Settings:
                     ))
                 except ValueError:
                     LOGGER.error(
-                        f'Invalid entry in {TOML_MAP_SPEED_TO_THICKNESS} -> {speed}={thickness} -> ignored'
+                        f'Invalid entry in {TomlSections.MAP_SPEED_TO_THICKNESS} -> {speed}={thickness} -> ignored'
                     )
                     continue
             LOGGER.info(
-                f'Valid entries in {TOML_MAP_SPEED_TO_THICKNESS} found: {len(self.__map_speed_to_thickness)}'  # noqa: E501
-                f'/{len(self.__user_data.get(TOML_MAP_SPEED_TO_THICKNESS, []))}'
+                f'Valid entries in {TomlSections.MAP_SPEED_TO_THICKNESS} found: {len(self.__map_speed_to_thickness)}'
+                f'/{len(self.__user_data.get(TomlSections.MAP_SPEED_TO_THICKNESS, []))}'
             )
         return self.__map_speed_to_thickness
 
@@ -543,7 +542,7 @@ class Settings:
     def protected_topologies(self) -> List[str]:
         if self.__protected_topologies is None:
             self.__protected_topologies = [str(topology) for topology in self.__user_data.get(
-                TOML_PROTECTED_TOPOLOGIES, []
+                TomlSections.PROTECTED_TOPOLOGIES, []
             )]
         return self.__protected_topologies
 
@@ -551,12 +550,12 @@ class Settings:
     def static_connections(self) -> List[StaticConnection]:
         if self.__static_connections is None:
             self.__static_connections = []
-            for connection in self.__user_data.get(TOML_STATIC_CONNECTIONS, []):
+            for connection in self.__user_data.get(TomlSections.STATIC_CONNECTIONS, []):
                 try:
                     left_host, left_service, right_service, right_host = connection
                 except ValueError:
                     LOGGER.error(
-                        f'Wrong entry in {TOML_STATIC_CONNECTIONS} -> {connection} -> ignored'
+                        f'Wrong entry in {TomlSections.STATIC_CONNECTIONS} -> {connection} -> ignored'
                     )
                     continue
                 if not right_host or not left_host:
@@ -571,14 +570,14 @@ class Settings:
                     left_host=str(left_host),
                 ))
             LOGGER.info(
-                f'Valid entries in {TOML_STATIC_CONNECTIONS} found: {len(self.__static_connections)}/'
-                f'{len(self.__user_data.get(TOML_STATIC_CONNECTIONS, []))}'
+                f'Valid entries in {TomlSections.STATIC_CONNECTIONS} found: {len(self.__static_connections)}/'
+                f'{len(self.__user_data.get(TomlSections.STATIC_CONNECTIONS, []))}'
             )
         return self.__static_connections
 
     @property
     def sites(self) -> List[str]:
         if self.__sites is None:
-            self.__sites = [str(site) for site in set(self.__user_data.get(TOML_SITES, [])) if is_valid_site_name(site)]
+            self.__sites = [str(site) for site in set(self.__user_data.get(TomlSections.SITES, [])) if is_valid_site_name(site)]
             LOGGER.info(f'Found {len(self.__sites)} to filter on')
         return self.__sites
diff --git a/source/bin/nvdct/lib/topologies.py b/source/bin/nvdct/lib/topologies.py
index 3fa4cbb..8594526 100755
--- a/source/bin/nvdct/lib/topologies.py
+++ b/source/bin/nvdct/lib/topologies.py
@@ -2,7 +2,6 @@
 # -*- coding: utf-8 -*-
 #
 # License: GNU General Public License v2
-import sys
 
 # Author: thl-cmk[at]outlook[dot]com
 # URL   : https://thl-cmk.hopto.org
@@ -11,10 +10,11 @@ import sys
 
 # 2024-12-22: refactoring topology creation into classes
 #             made L3 topology IP version independent
+# 2024-12-25: refactoring, moved function into classes
 
 from abc import abstractmethod
-from collections.abc import Mapping, MutableMapping, Sequence
-from ipaddress import ip_address, ip_network, ip_interface
+from collections.abc import Mapping, MutableMapping, Sequence, MutableSet
+from ipaddress import ip_address, ip_interface, ip_network
 from re import sub as re_sub
 from typing import Dict, List, Tuple
 
@@ -24,15 +24,13 @@ from lib.backends import (
 )
 from lib.constants import (
     CACHE_INTERFACES_DATA,
-    HOST_LABEL_L3V4_HOSTS,
-    HOST_LABEL_L3V4_ROUTER,
-    HOST_LABEL_L3V6_HOSTS,
-    HOST_LABEL_L3V6_ROUTER,
+    HostLabels,
     IPVersion,
+    InvPaths,
     LOGGER,
-    PATH_INTERFACES,
-    PATH_L3,
-    DATAPATH,
+    L2InvColumns,
+    L3InvColumns,
+    TomlSections,
 )
 from lib.settings import (
     Emblems,
@@ -41,32 +39,36 @@ from lib.settings import (
     Wildcard,
 )
 from lib.utils import (
-    InventoryColumns,
-    IpInfo,
-    # is_valid_hostname,
     save_data_to_file,
 )
 
 
 class NvObjects:
-    def __init__(self) -> None:
+    def __init__(
+            self,
+            host_cache: HostCache
+    ) -> None:
         self.nv_objects: Dict[str, any] = {}
         self.host_count: int = 0
         self.host_list: List[str] = []
+        self.host_cache = host_cache
 
     def add_host(
-        self,
-        host: str,
-        host_cache: HostCache,
-        emblem: str | None = None
+            self,
+            host: str,
+            emblem: str | None = None,
+            name: str | None = None,
     ) -> None:
+        if name and host in self.nv_objects:
+            self.nv_objects[host]['name'] = name
+
         if host not in self.nv_objects:
             self.host_count += 1
             self.host_list.append(host)
             link: Dict = {}
             metadata: Dict = {}
             # LOGGER.debug(f'host: {host}, {host_cache.host_exists(host=host)}')
-            if host_cache.host_exists(host=host) is True:
+            if self.host_cache.host_exists(host=host) is True:
                 LOGGER.debug(f'host: {host} exists')
                 link = {'core': host}
             else:
@@ -82,31 +84,30 @@ class NvObjects:
                     }
 
             self.nv_objects[host] = {
-                'name': host,
+                'name': name if name is not None else host,
                 'link': link,
                 'metadata': metadata,
             }
             LOGGER.debug(f'host: {host}, link: {link}, metadata: {metadata}')
 
     def add_service(
-        self,
-        host: str,
-        service: str,
-        host_cache: HostCache,
-        emblem: str | None = None,
-        metadata: Dict | None = None,
-        name: str | None = None,
+            self,
+            host: str,
+            service: str,
+            emblem: str | None = None,
+            metadata: Dict | None = None,
+            name: str | None = None,
     ) -> None:
         if metadata is None:
             metadata = {}
         if name is None:
             name = service
 
-        self.add_host(host=host, host_cache=host_cache)
+        self.add_host(host=host)
         service_object = f'{service}@{host}'
         if service_object not in self.nv_objects:
             link: Dict = {}
-            if host_cache.host_exists(host=host):
+            if self.host_cache.host_exists(host=host):
                 link = {'core': [host, service]}
             elif emblem is not None:
                 metadata.update({
@@ -125,16 +126,14 @@ class NvObjects:
         elif metadata is not {}:
             self.nv_objects[service_object]['metadata'].update(metadata)
 
-
     def add_interface(
-        self,
-        host: str,
-        service: str,
-        host_cache: HostCache,
-        emblem: str | None = None,
-        metadata: Dict | None = None,
-        name: str | None = None,
-        item: str | None = None,
+            self,
+            host: str,
+            service: str,
+            item: str | None,
+            emblem: str | None = None,
+            metadata: Dict | None = None,
+            name: str | None = None,
     ) -> None:
         if metadata is None:
             metadata = {}
@@ -142,16 +141,16 @@ class NvObjects:
             name = service
         speed = None
 
-        self.add_host(host=host, host_cache=host_cache)
+        self.add_host(host=host)
         service_object = f'{service}@{host}'
         if service_object not in self.nv_objects:
             link: Dict = {}
-            if item is None:
-                item = get_service_by_interface(host, service.lstrip('0'), host_cache)
-            if item and host_cache.host_exists(host=host):
+            # if item is None:
+            #     item = get_service_by_interface(host, service.lstrip('0'), host_cache)
+            if item and self.host_cache.host_exists(host=host):
                 service_long = f'Interface {item}'
                 link = {'core': [host, service_long]}
-                if op_data := get_operational_interface_data(host, item, host_cache):
+                if op_data := self.get_operational_interface_data(host, item):
                     metadata.update(op_data)
                     speed = op_data.get('op_speed_int')
             elif emblem is not None:
@@ -176,11 +175,11 @@ class NvObjects:
             self.nv_objects[service_object]['metadata'].update(metadata)
 
     def add_ip_address(
-        self,
-        host: str,
-        raw_ip_address: str,
-        emblem: str,
-        interface: str | None,
+            self,
+            host: str,
+            raw_ip_address: str,
+            emblem: str,
+            interface: str | None,
     ) -> None:
         if interface is not None:
             service_object = f'{raw_ip_address}@{interface}@{host}'
@@ -204,6 +203,54 @@ class NvObjects:
                 }
             }
 
+    def get_operational_interface_data(
+            self,
+            host: str,
+            item: str,
+    ) -> Dict[str, str | int] | None:
+        unit_to_bits_per_second = {
+            'Bit/s': 1,
+            'kBit/s': 1000,
+            'Kbps': 1000,
+            'MBit/s': 1000000,
+            'Mbps': 1000000,
+            'GBit/s': 1000000000,
+            'Gbps': 1000000000,
+        }
+
+        # get dict of interfaces with the item as key
+        interface_data: Dict[str, any] | None = self.host_cache.get_data(
+            host=host, item=CacheItems.interfaces, path=CACHE_INTERFACES_DATA
+        )
+        try:
+            raw_operational_data = interface_data[item]['long_plugin_output']
+        except (KeyError, TypeError):
+            return None
+
+        if raw_operational_data:
+            operational_data: Dict[str, str | int] = {}
+            for _entry in raw_operational_data:
+                try:
+                    key, value = _entry.split(': ', 1)  # split only at the first colon
+                except ValueError:
+                    continue
+                value = value.strip(' ')
+                match key:
+                    case 'MAC':
+                        if len(value) == 17:  # valid MAC: 6C:DD:30:DD:51:8B'
+                            operational_data['mac'] = value
+                    case 'Speed':
+                        try:  # *_ -> ignore rest of string, i.e: (expected: 1 GBit/s)WARN
+                            speed, unit, *_ = value.split(' ')
+                        except ValueError:
+                            pass
+                        else:
+                            operational_data['op_sped_str'] = f'{speed} {unit}'
+                            operational_data['op_speed_int'] = int(float(speed) * unit_to_bits_per_second[unit])
+
+            return operational_data
+        return None
+
     def add_ip_network(self, network: str, emblem: str, ) -> None:
         if network not in self.nv_objects:
             self.nv_objects[network] = {
@@ -254,9 +301,9 @@ class NvConnections:
             self.nv_connections.append([connection])
 
     def add_meta_data_to_connections(
-        self,
-        nv_objects: NvObjects,
-        speed_map: Sequence[Thickness],
+            self,
+            nv_objects: NvObjects,
+            speed_map: Sequence[Thickness],
     ):
         for connection in self.nv_connections:
             warning = False
@@ -281,7 +328,6 @@ class NvConnections:
             left_native_vlan = nv_objects.nv_objects[left].get('metadata', {}).get('native_vlan')
             right_native_vlan = nv_objects.nv_objects[right].get('metadata', {}).get('native_vlan')
 
-
             if right_speed and left_speed:
                 right_thickness = map_speed_to_thickness(right_speed, speed_map)
                 # left_thickness = map_speed_to_thickness(left_speed, speed_map)
@@ -297,7 +343,7 @@ class NvConnections:
                     # metadata = add_tooltip_quickinfo(metadata, right, right_speed_str)
 
                     LOGGER.warning(
-                        f'Connection with speed mismatch: {left} (speed: {left_speed_str})'
+                        f'Connection speed mismatch: {left} (speed: {left_speed_str})'
                         f'<->{right} (speed: {right_speed_str})'
                     )
 
@@ -312,7 +358,7 @@ class NvConnections:
                     )
 
                     LOGGER.warning(
-                        f'Connection with duplex mismatch: {left} (duplex: {left_duplex})'
+                        f'Connection duplex mismatch: {left} (duplex: {left_duplex})'
                         f'<->{right} (duplex: {right_duplex})'
                     )
             if left_native_vlan and right_native_vlan:
@@ -325,13 +371,13 @@ class NvConnections:
                         )
 
                         LOGGER.warning(
-                            f'Connection with native vlan mismatch: '
+                            f'Connection native vlan mismatch: '
                             f'{left} (vlan: {left_native_vlan})<->{right} (vlan: {right_native_vlan})'
                         )
             if warning:
                 metadata['line_config'].update({
-                        'color': 'red',
-                        'thickness': 5,
+                    'color': 'red',
+                    'thickness': 5,
                 })
                 metadata['line_config']['css_styles']['stroke-dasharray'] = '10 5'
                 nv_objects.add_icon_to_object(left, 'icon_warning')
@@ -345,17 +391,20 @@ class Topology:
             self,
             emblems: Emblems,
             host_cache: HostCache,
+            topology: str,
     ):
-        self.nv_objects: NvObjects = NvObjects()
+        self.nv_objects: NvObjects = NvObjects(host_cache=host_cache)
         self.nv_connections: NvConnections = NvConnections()
         self.emblems: Emblems = emblems
         self.host_cache: HostCache = host_cache
+        self.topology = topology
 
     @abstractmethod
     def create(self):
         raise NotImplementedError
 
-    def save(self, label:str, output_directory: str, make_default: bool):
+    def save(self, label: str, output_directory: str, make_default: bool, dont_compare):
+        LOGGER.info(f'{self.topology} saving...')
         data = {
             'version': 1,
             'name': label,
@@ -364,13 +413,130 @@ class Topology:
         }
         save_data_to_file(
             data=data,
-            path=(
-                f'{DATAPATH}/{output_directory}'
-            ),
+            path=output_directory,
             file=f'data_{label}.json',
             make_default=make_default,
+            dont_compare=dont_compare,
         )
 
+    def get_service_by_interface(self, host: str, interface: str) -> str | None:
+        """
+        Returns:
+            Tuple of interface item
+        """
+
+        short_if_names = {
+            'ethernet': 'eth',
+            # 'fastethernet': 'Fa',
+            # 'gigabitethernet': 'gi',
+            # 'tengigabitethernet': 'te',
+            # 'fortygigabitethernet': 'Fo',
+            # 'hundredgigabitethernet': 'Hu',
+            # 'management': 'Ma',
+        }
+
+        def get_short_if_name(long_interface: str) -> str:
+            """
+            returns short interface name from long interface name
+            interface: is the long interface name
+            :type long_interface: str
+            """
+            if not long_interface:
+                return long_interface
+            for interface_prefix in short_if_names.keys():
+                if long_interface.lower().startswith(interface_prefix.lower()):
+                    interface_short = short_if_names[interface_prefix]
+                    return long_interface.lower().replace(interface_prefix.lower(), interface_short, 1)
+            return long_interface
+
+        # try to find the item for an interface
+        def match_entry_with_item(interface_entry: Mapping[str, str], services: Sequence[str]) -> str | None:
+            values = [
+                interface_entry.get('name'.strip()),
+                interface_entry.get('description'.strip()),
+                interface_entry.get('alias').strip()
+            ]
+            for value in values:
+                if value in services:
+                    return value
+
+            index = str(interface_entry.get('index'))
+
+            # try alias+index
+            alias_index = str(interface_entry.get('alias')).strip() + ' ' + index
+            if alias_index in services:
+                LOGGER.info(f'{self.topology} match found by alias-index|{interface_entry}| <-> |{alias_index}|')
+                return alias_index
+
+            # try description+index
+            description_index = str(interface_entry.get('description')).strip() + ' ' + index
+            if description_index in services:
+                LOGGER.info(f'{self.topology} match found by description-index|{interface_entry}| <-> |{description_index}|')
+                return description_index
+
+            # for index try with padding
+            pad_services: List[str] = [x for x in services if x.isdigit()]
+            if pad_services:
+                max_pad: int = len(max(pad_services, key=len)) + 1
+                # min_pad: int = len(min(pad_services, key=len))
+                min_pad: int = len(min(pad_services, key=len))
+                for i in range(min_pad, max_pad):
+                    index_padded = f'{index:0>{i}}'
+                    if index_padded in pad_services:
+                        return index_padded
+                    # still not found try values + index
+                    for value in values:
+                        if f'{value} {index_padded}' in services:
+                            return f'{value} {index_padded}'
+
+            LOGGER.warning(f'{self.topology} no match found |{interface_entry}| <-> |{services}|')
+            return None
+
+        # empty host/neighbour should never happen here
+        if not host:
+            LOGGER.warning(f'{self.topology} no host name |{host}|')
+            return None
+
+        # get dict of interfaces with the item as key
+        interface_data: Mapping[str, Mapping[str, object]] = self.host_cache.get_data(
+            host=host, item=CacheItems.interfaces, path=CACHE_INTERFACES_DATA
+        )
+        if not interface_data:
+            LOGGER.warning(f'{self.topology} no interface data for: {host}')
+            return None
+
+        # try to find the interface in the host interface inventory list
+        inventory = self.host_cache.get_data(
+            host=host, item=CacheItems.inventory, path=InvPaths.INTERFACES
+        )
+        if not inventory:
+            LOGGER.warning(f'{self.topology} no interface inventory for: {host}')
+            return None
+
+        interface_items: Sequence[str] = list(interface_data.keys())
+
+        # the easy case
+        if interface in interface_items:
+            return interface
+
+        for entry in inventory:
+            if interface in [
+                entry.get('name'),
+                entry.get('description'),
+                entry.get('alias'),
+                str(entry.get('index')),
+                entry.get('phys_address'),
+            ]:
+                return match_entry_with_item(entry, interface_items)
+            elif f'1:{interface}' == entry.get('name'):  # Extreme non stack:
+                return match_entry_with_item(entry, interface_items)
+            elif entry.get('name') is not None and get_short_if_name(
+                    entry.get('name')) == str(interface).lower():  # Cisco NXOS
+                return match_entry_with_item(entry, interface_items)
+
+        LOGGER.warning(msg=f'{self.topology} Device: {host}: service for interface |{interface}| not found')
+
+
 class TopologyStatic(Topology):
     def __init__(
             self,
@@ -381,26 +547,24 @@ class TopologyStatic(Topology):
         super().__init__(
             emblems=emblems,
             host_cache=host_cache,
+            topology='[STATIC]',
         )
         self.connections: Sequence[StaticConnection] = connections
 
     def create(self):
         for connection in self.connections:
-            LOGGER.info(msg=f'connection: {connection}')
+            LOGGER.debug(msg=f'{self.topology} connection from {TomlSections.STATIC_CONNECTIONS}: {connection}')
             self.nv_objects.add_host(
                 host=connection.right_host,
-                host_cache=self.host_cache,
                 emblem=self.emblems.host_node
             )
             self.nv_objects.add_host(
                 host=connection.left_host,
-                host_cache=self.host_cache,
                 emblem=self.emblems.host_node
             )
             if connection.right_service:
                 self.nv_objects.add_service(
                     host=connection.right_host,
-                    host_cache=self.host_cache,
                     emblem=self.emblems.service_node,
                     service=connection.right_service
                 )
@@ -412,7 +576,6 @@ class TopologyStatic(Topology):
             if connection.left_service:
                 self.nv_objects.add_service(
                     host=connection.left_host,
-                    host_cache=self.host_cache,
                     emblem=self.emblems.service_node,
                     service=connection.left_service
                 )
@@ -442,118 +605,102 @@ class TopologyStatic(Topology):
                     right=f'{connection.left_host}',
                 )
 
+
 class TopologyL2(Topology):
     def __init__(
             self,
             emblems: Emblems,
             host_cache: HostCache,
-            case: str,
-            inv_columns: InventoryColumns,
-            l2_drop_hosts: List[str],
-            l2_host_map: Dict[str, str],
+            l2_drop_neighbours: List[str],
             l2_neighbour_replace_regex: List[Tuple[str, str]],
             label: str,
             path_in_inventory: str,
-            prefix: str,
-            remove_domain: bool,
             seed_devices: Sequence[str],
+            display_l2_neighbours: bool,
     ):
         super().__init__(
             emblems=emblems,
             host_cache=host_cache,
+            topology = f'[L2 {label}]',
         )
-        self.case: str = case
-        self.inv_columns: InventoryColumns = inv_columns
-        self.l2_drop_hosts: List[str] = l2_drop_hosts
-        self.l2_host_map: Dict[str, str] = l2_host_map
-        self.l2_neighbour_replace_regex: List[Tuple[str, str]] = l2_neighbour_replace_regex
+        self.l2_drop_neighbours: List[str] = l2_drop_neighbours
         self.label: str = label
         self.neighbour_to_host: MutableMapping[str, str] = {}
         self.path_in_inventory: str = path_in_inventory
-        self.prefix: str = prefix
-        self.remove_domain: bool = remove_domain
-        self.seed_devices: Sequence[str] = seed_devices
+        self.hosts_to_go: MutableSet[str] = set(seed_devices)
+        self.raw_neighbour_to_neighbour: Dict[str, str] = {}
+        self.l2_neighbour_replace_regex: List[Tuple[str, str]] = l2_neighbour_replace_regex
+        self.hosts_done: MutableSet[str] = set()
+        self.display_l2_neighbours: bool = display_l2_neighbours
 
     def create(self):
-        if not (devices_to_go := list(set(self.seed_devices))):  # remove duplicates
-            LOGGER.error('No seed devices configured!')
+        if not self.hosts_to_go:
+            LOGGER.error(f'{self.topology} no seed devices !')
             return
 
-        devices_done = []
+        while self.hosts_to_go:
+            host = self.hosts_to_go.pop()
+            self.hosts_done.add(host)
 
-        while devices_to_go:
-            device = devices_to_go[0]
-
-            if device in self.l2_host_map.keys():
-                try:
-                    devices_to_go.remove(device)
-                except ValueError:
-                    pass
-                device = self.l2_host_map[device]
-                if device in devices_done:
-                    continue
-
-            topo_data = self.host_cache.get_data(
-                host=device, item=CacheItems.inventory, path=self.path_in_inventory
+            topo_data: Sequence[Mapping[str, str]] = self.host_cache.get_data(
+                host=host, item=CacheItems.inventory, path=self.path_in_inventory
             )
             if topo_data:
-                self.device_from_inventory(
-                    host=device,
+                self.host_from_inventory(
+                    host=host,
                     inv_data=topo_data,
                 )
 
-                for _entry in self.nv_objects.host_list:
-                    if _entry not in devices_done:
-                        devices_to_go.append(_entry)
-
-            devices_to_go = list(set(devices_to_go))
-            devices_done.append(device)
-            devices_to_go.remove(device)
-            LOGGER.info(msg=f'Device done: {device}, source: {self.label}')
+            LOGGER.info(msg=f'{self.topology} host done : {host}')
 
-    def device_from_inventory(
+    def host_from_inventory(
             self,
             host: str,
-            inv_data,
+            inv_data: Sequence[Mapping[str,str]],
     ):
         for topo_neighbour in inv_data:
             # check if required data are not empty
-            if not (neighbour := topo_neighbour.get(self.inv_columns.neighbour)):
-                LOGGER.warning(f'incomplete data, neighbour missing {topo_neighbour}')
+            if not (raw_neighbour := topo_neighbour.get(L2InvColumns.NEIGHBOUR)):
+                LOGGER.warning(f'{self.topology} incomplete data: neighbour missing {topo_neighbour}')
                 continue
-            if not (raw_local_port := topo_neighbour.get(self.inv_columns.local_port)):
-                LOGGER.warning(f'incomplete data, local port missing {topo_neighbour}')
+            if not (raw_local_port := topo_neighbour.get(L2InvColumns.LOCALPORT)):
+                LOGGER.warning(f'{self.topology} incomplete data: local port missing {topo_neighbour}')
                 continue
-            if not (raw_neighbour_port := topo_neighbour.get(self.inv_columns.neighbour_port)):
-                LOGGER.warning(f'incomplete data, neighbour port missing {topo_neighbour}')
+            if not (raw_neighbour_port := topo_neighbour.get(L2InvColumns.NEIGHBOURPORT)):
+                LOGGER.warning(f'{self.topology} incomplete data: neighbour port missing {topo_neighbour}')
                 continue
 
-            if not (neighbour:= self.get_host_from_neighbour(neighbour)):
+            if not (neighbour := self.adjust_raw_neighbour(raw_neighbour)):
                 continue
 
+            if neighbour_host := self.host_cache.get_host_from_neighbour(neighbour):
+                if neighbour_host not in self.hosts_done:
+                    self.hosts_to_go.add(neighbour_host)
+            else:
+                neighbour_host = neighbour
+
             # getting/checking interfaces
-            local_port = get_service_by_interface(host, raw_local_port, self.host_cache)
+            local_port = self.get_service_by_interface(host, raw_local_port)
             if not local_port:
                 local_port = raw_local_port
-                LOGGER.warning(msg=f'service not found: host: {host}, raw_local_port: {raw_local_port}')
+                LOGGER.warning(msg=f'{self.topology} service not found for local_port: {host}, {raw_local_port}')
             elif local_port != raw_local_port:
                 # local_port = raw_local_port  # don't reset local_port
                 LOGGER.info(
-                    msg=f'host: {host}, raw_local_port: {raw_local_port} -> local_port: {local_port}'
+                    msg=f'{self.topology} map raw_local_port -> local_port: {host}, {raw_local_port} -> {local_port}'
                 )
 
-            neighbour_port = get_service_by_interface(neighbour, raw_neighbour_port, self.host_cache)
+            neighbour_port = self.get_service_by_interface(neighbour_host, raw_neighbour_port)
             if not neighbour_port:
                 neighbour_port = raw_neighbour_port
                 LOGGER.warning(
-                    msg=f'service not found: neighbour: {neighbour}, '
-                        f'raw_neighbour_port: {raw_neighbour_port}'
+                    msg=f'{self.topology} service not found for neighbour port:  {neighbour_host}, {raw_neighbour_port}'
                 )
             elif neighbour_port != raw_neighbour_port:
                 # neighbour_port = raw_neighbour_port  # don't reset neighbour_port
                 LOGGER.info(
-                    msg=f'neighbour: {neighbour}, raw_neighbour_port {raw_neighbour_port} '
-                        f'-> neighbour_port {neighbour_port}'
+                    msg=f'{self.topology} map raw_neighbour_port -> neighbour_port: {neighbour_host}, {raw_neighbour_port} '
+                        f'-> {neighbour_port}'
                 )
 
             metadata = {
@@ -561,83 +708,79 @@ class TopologyL2(Topology):
                 'native_vlan': topo_neighbour.get('native_vlan'),
             }
 
-            self.nv_objects.add_host(host=host, host_cache=self.host_cache)
-            self.nv_objects.add_host(host=neighbour, host_cache=self.host_cache)
+            self.nv_objects.add_host(
+                host=host,
+                emblem=self.emblems.host_node,
+            )
+            self.nv_objects.add_host(
+                host=neighbour_host,
+                name=raw_neighbour if self.display_l2_neighbours else None,
+                emblem=self.emblems.host_node,
+            )
             self.nv_objects.add_interface(
                 host=str(host),
                 service=str(local_port),
-                host_cache=self.host_cache,
                 metadata=metadata,
                 name=str(raw_local_port),
-                item=str(local_port)
+                item=str(local_port),
+                emblem=self.emblems.service_node,
             )
             self.nv_objects.add_interface(
-                host=str(neighbour),
+                host=str(neighbour_host),
                 service=str(neighbour_port),
-                host_cache=self.host_cache,
                 name=str(raw_neighbour_port),
-                item=str(neighbour_port)
+                item=str(neighbour_port),
+                emblem=self.emblems.service_node,
             )
             self.nv_connections.add_connection(
                 left=str(host),
                 right=f'{local_port}@{host}',
             )
             self.nv_connections.add_connection(
-                left=str(neighbour),
-                right=f'{neighbour_port}@{neighbour}',
+                left=str(neighbour_host),
+                right=f'{neighbour_port}@{neighbour_host}',
             )
             self.nv_connections.add_connection(
                 left=f'{local_port}@{host}',
-                right=f'{neighbour_port}@{neighbour}',
+                right=f'{neighbour_port}@{neighbour_host}',
             )
 
-    def get_host_from_neighbour(self, neighbour: str) -> str | None:
+    def adjust_raw_neighbour(self, raw_neighbour: str) -> str | None:
+        """
+        Checks if neighbour should be dropped or adjusted via regex.
+        The next request for the same neighbour will be served from cache.
+        Args:
+            raw_neighbour: the neighbour name to check/adjust
+
+        Returns:
+            the adjusted neighbour name or None
+        """
         try:
-            return self.neighbour_to_host[neighbour]
+            return self.neighbour_to_host[raw_neighbour]
         except KeyError:
             pass
 
-        if neighbour in self.l2_drop_hosts:
-            LOGGER.info(msg=f'drop neighbour: {neighbour}')
-            self.neighbour_to_host[neighbour] = None
+        if raw_neighbour in self.l2_drop_neighbours:
+            LOGGER.info(msg=f'{self.topology} drop in {TomlSections.L2_DROP_NEIGHBOURS}: {raw_neighbour}')
+            self.neighbour_to_host[raw_neighbour] = None
             return None
 
+        adjusted_neighbour = raw_neighbour
         if self.l2_neighbour_replace_regex:
             for re_str, replace_str in self.l2_neighbour_replace_regex:
-                re_neighbour = re_sub(re_str, replace_str, neighbour)
-                if re_neighbour != neighbour:
-                    LOGGER.info(f'regex changed Neighbor |{neighbour}| to |{re_neighbour}|')
-                    neighbour = re_neighbour
-                if not neighbour:
-                    LOGGER.info(f'Neighbour removed by regex (|{neighbour}|, |{re_str}|, |{replace_str}|)')
-                    break
-            if not neighbour:
-                self.neighbour_to_host[neighbour] = None
-                return None
-
-        if self.remove_domain:
-            neighbour = neighbour.split('.')[0]
-
-        # drop neighbour after domain split
-        if neighbour in self.l2_drop_hosts:
-            LOGGER.info(msg=f'drop neighbour: {neighbour}')
-            self.neighbour_to_host[neighbour] = None
-            return None
+                re_neighbour = re_sub(re_str, replace_str, adjusted_neighbour)
+                if not re_neighbour:
+                    LOGGER.info(f'{self.topology} removed by {TomlSections.L2_NEIGHBOUR_REPLACE_REGEX}: (|{adjusted_neighbour}|, |{re_str}|, |{replace_str}|)')
+                    self.neighbour_to_host[raw_neighbour] = None
+                    return None
 
-        if self.case == 'UPPER':
-            neighbour = neighbour.upper()
-            LOGGER.debug(f'Changed neighbour to upper case: {neighbour}')
-        elif self.case == 'LOWER':
-            neighbour = neighbour.lower()
-            LOGGER.debug(f'Changed neighbour to lower case: {neighbour}')
+                if re_neighbour != adjusted_neighbour:
+                    LOGGER.info(f'{self.topology} changed by {TomlSections.L2_NEIGHBOUR_REPLACE_REGEX} |{adjusted_neighbour}| to |{re_neighbour}|')
+                    adjusted_neighbour = re_neighbour
 
-        if self.prefix:
-            neighbour = f'{self.prefix}{neighbour}'
-        # rewrite neighbour if inventory neighbour and checkmk host don't match
-        if neighbour in self.l2_host_map.keys():
-            neighbour = self.l2_host_map[neighbour]
+        self.neighbour_to_host[raw_neighbour] = adjusted_neighbour
+        return adjusted_neighbour
 
-        return neighbour
 
 class TopologyL3(Topology):
     def __init__(
@@ -648,172 +791,209 @@ class TopologyL3(Topology):
             ignore_ips: Sequence[ip_network],
             ignore_wildcard: Sequence[Wildcard],
             include_hosts: bool,
+            include_loopback: bool,
             replace: Mapping[str, str],
+            skip_cidr_0: bool,
+            skip_cidr_32_128: bool,
             skip_if: bool,
             skip_ip: bool,
+            skip_public: bool,
             summarize: Sequence[ip_network],
             version: int
     ):
         super().__init__(
             emblems=emblems,
             host_cache=host_cache,
+            topology=f'[L3 IPv{version}]'
         )
         self.ignore_hosts: Sequence[str] = ignore_hosts
         self.ignore_ips: Sequence[ip_network] = ignore_ips
         self.ignore_wildcard: Sequence[Wildcard] = ignore_wildcard
         self.include_hosts: bool = include_hosts
         self.replace: Mapping[str, str] = replace
+        self.skip_cidr_0: bool = skip_cidr_0
+        self.skip_cidr_32_128: bool = skip_cidr_32_128
         self.skip_if: bool = skip_if
         self.skip_ip: bool = skip_ip
+        self.skip_public: bool = skip_public
+        self.show_loopback: bool = include_loopback
         self.summarize: Sequence[ip_network] = summarize
         self.version = version
 
     def create(self):
         match self.version:
             case IPVersion.IPv4:
-                host_list: Sequence[str] = self.host_cache.get_hosts_by_label(HOST_LABEL_L3V4_ROUTER)
+                host_list: Sequence[str] = self.host_cache.get_hosts_by_label(HostLabels.L3V4_ROUTER)
 
                 if self.include_hosts:
-                    host_list += self.host_cache.get_hosts_by_label(HOST_LABEL_L3V4_HOSTS)
+                    host_list += self.host_cache.get_hosts_by_label(HostLabels.L3V4_HOSTS)
 
             case IPVersion.IPv6:
-                host_list: Sequence[str] = self.host_cache.get_hosts_by_label(HOST_LABEL_L3V6_ROUTER)
+                host_list: Sequence[str] = self.host_cache.get_hosts_by_label(HostLabels.L3V6_ROUTER)
 
                 if self.include_hosts:
-                    host_list += self.host_cache.get_hosts_by_label(HOST_LABEL_L3V6_HOSTS)
+                    host_list += self.host_cache.get_hosts_by_label(HostLabels.L3V6_HOSTS)
 
             case _:
                 host_list = []
 
-        LOGGER.debug(f'host list: {host_list}')
+        LOGGER.debug(f'{self.topology} host to work on: {host_list}')
         if not host_list:
-            LOGGER.warning(
-                msg='No (routing capable) host found. Check if "inv_ip_addresses.mkp" '
+            LOGGER.error(
+                msg=f'{self.topology} No (routing capable) host found. Check if "inv_ip_addresses.mkp" '
                     'added/enabled and inventory and host label discovery has run.'
             )
             return
 
-        LOGGER.debug(f'L3 ignore hosts: {self.ignore_hosts}')
+        LOGGER.debug(f'{self.topology} ignore hosts: {self.ignore_hosts}')
         for raw_host in host_list:
             host = raw_host
             if host in self.ignore_hosts:
-                LOGGER.info(f'L3 host {host} ignored')
+                LOGGER.info(f'{self.topology} host ignored in {TomlSections.L3_IGNORE_HOSTS}: {host}')
                 continue
             if not (inv_ip_addresses := self.host_cache.get_data(
-                    host=host, item=CacheItems.inventory, path=PATH_L3)
+                    host=host, item=CacheItems.inventory, path=InvPaths.L3)
             ):
-                LOGGER.warning(f'No IP address inventory found for host: {host}')
+                LOGGER.warning(f'{self.topology} no IP address inventory found for host: {host}')
                 continue
 
-            self.nv_objects.add_host(host=host, host_cache=self.host_cache)
+            self.nv_objects.add_host(host=host)
             for inv_ip_address in inv_ip_addresses:
                 emblem = self.emblems.ip_network
                 try:
-                    ip_info = IpInfo(
-                        address=inv_ip_address['address'],
-                        device=inv_ip_address['device'],
-                        broadcast=inv_ip_address['broadcast'],
-                        cidr=inv_ip_address['cidr'],
-                        netmask=inv_ip_address['netmask'],
-                        network=inv_ip_address['network'],
-                        type=inv_ip_address['type'],
-                        scope_id=inv_ip_address.get('scope_id'),  # this is an optional field
+                    device = inv_ip_address[L3InvColumns.DEVICE]
+                    interface_address: ip_interface = ip_interface(
+                        f'{inv_ip_address[L3InvColumns.ADDRESS]}/{inv_ip_address[L3InvColumns.CIDR]}'
                     )
-                except KeyError:
-                    LOGGER.warning(f'Drop IP address data for host: {host}, data: {inv_ip_address}')
+                except KeyError as e:
+                    LOGGER.warning(f'{self.topology} drop IP missing data: {host}, {inv_ip_address}, {e}.')
+                    continue
+
+                # skip entries without prefix-length/netmask
+                if self.skip_cidr_0 and interface_address.network.prefixlen == 0:
+                    LOGGER.info(f'{self.topology} drop IP with CIDR "0": {host}, {interface_address.compressed}')
                     continue
 
-                interface_address: ip_interface = ip_interface(f'{ip_info.address}/{ip_info.cidr}')
                 if interface_address.version != self.version:
                     LOGGER.info(
-                        f'host: {host} dropped non IPv{self.version} address: {ip_info.address},'
-                        f' type: {ip_info.type}'
+                        f'{self.topology} drop IP non IPv{self.version} version: {host}, {interface_address.compressed}'
                     )
                     continue
 
-                if interface_address.is_loopback:
-                    LOGGER.info(f'host: {host} dropped loopback address: {ip_info.address}')
+                if not self.show_loopback and interface_address.is_loopback:
+                    LOGGER.info(f'{self.topology} drop IP is loopback: {host},  {interface_address.ip.compressed}')
                     continue
 
-                if interface_address.is_link_local:
-                    LOGGER.info(f'host: {host} dropped link-local address: {ip_info.address}')
+                if self.skip_public and not interface_address.is_private:
+                    LOGGER.info(f'{self.topology} drop IP is public: {host},  {interface_address.ip.compressed}')
                     continue
 
-                # if interface_address.network.prefixlen == 32 or interface_address.network.prefixlen == 128: # drop host addresses
-                #     LOGGER.info(
-                #         f'host: {host} dropped host address: {ip_info.address}/{ip_info.cidr}'
-                #     )
-                #     continue
+                if interface_address.is_link_local and interface_address.version == 6:
+                    LOGGER.info(f'{self.topology} drop IP is link-local: {host}, {interface_address.ip.compressed}')
+                    continue
 
-                if is_ignore_ip(ip_info.address, self.ignore_ips):
-                    LOGGER.info(f'host: {host} dropped ignore address: {ip_info.address}')
+                # drop host addresses  /32 or /128 -> one IP ine network
+                if self.skip_cidr_32_128 and interface_address.network.num_addresses == 1:
+                    LOGGER.info(f'{self.topology} drop IP with CIDR (32 or /128): {host}, {interface_address.compressed}')
                     continue
 
-                if is_ignore_wildcard(ip_info.address, self.ignore_wildcard):
-                    LOGGER.info(f'host: {host} dropped wildcard address: {ip_info.address}')
+                if self.is_ignore_ip(interface_address.ip.compressed):
+                    LOGGER.info(f'{self.topology} rop IP in {TomlSections.L3_IGNORE_IP}: {host}, {interface_address.compressed}')
                     continue
 
-                if network := get_network_summary(
-                        raw_ip_address=ip_info.address,
-                        summarize=self.summarize,
-                ):
+                if self.is_ignore_wildcard(interface_address.ip.compressed):
+                    LOGGER.info(f'{self.topology} drop IP in {TomlSections.L3V4_IGNORE_WILDCARD}: {host}, {interface_address.compressed}')
+                    continue
+
+                if network := self.get_network_summary(raw_ip_address=interface_address.ip.compressed):
                     emblem = self.emblems.l3_summarize
                     LOGGER.info(
-                        f'Network summarized: {ip_info.network}/{ip_info.cidr} -> {network}'
+                        f'{self.topology} Summarized IP in {TomlSections.L3_SUMMARIZE}: {interface_address.compressed} -> {network}'
                     )
                 else:
-                    network = f'{ip_info.network}/{ip_info.cidr}'
+                    network = f'{interface_address.network.compressed}'
 
                 if network in self.replace.keys():
-                    LOGGER.info(f'Replaced network {network} with {self.replace[network]}')
+                    LOGGER.info(f'{self.topology} Replaced network in {TomlSections.L3_REPLACE}: {network} -> {self.replace[network]}')
                     network = self.replace[network]
                     emblem = self.emblems.l3_replace
 
                 self.nv_objects.add_ip_network(network=network, emblem=emblem)
 
+                item = None
+                if not self.skip_if:
+                    item = self.get_service_by_interface(host=host, interface=device)
+
                 if self.skip_if is True and self.skip_ip is True:
                     self.nv_connections.add_connection(left=host, right=network)
                 elif self.skip_if is True and self.skip_ip is False:
                     self.nv_objects.add_ip_address(
                         host=host,
                         interface=None,
-                        raw_ip_address=ip_info.address,
+                        raw_ip_address=interface_address.ip.compressed,
                         emblem=self.emblems.ip_address,
                     )
                     self.nv_objects.add_tooltip_quickinfo(
-                        '{ip_info.address}@{host}', 'Interface', ip_info.device
+                        f'{interface_address.ip.compressed}@{host}', 'Interface', device
                     )
-                    self.nv_connections.add_connection(left=f'{host}', right=f'{ip_info.address}@{host}')
-                    self.nv_connections.add_connection(left=network, right=f'{ip_info.address}@{host}')
+                    self.nv_connections.add_connection(left=f'{host}', right=f'{interface_address.ip.compressed}@{host}')
+                    self.nv_connections.add_connection(left=network, right=f'{interface_address.ip.compressed}@{host}')
                 elif self.skip_if is False and self.skip_ip is True:
-                    self.nv_objects.add_interface(
-                        host=host, service=ip_info.device, host_cache=self.host_cache
-                    )
+                    self.nv_objects.add_interface(host=host, service=device, item=item)
                     self.nv_objects.add_tooltip_quickinfo(
-                        f'{ip_info.device}@{host}', 'IP-address', ip_info.address
+                        f'{device}@{host}', 'IP-address', interface_address.ip.compressed
                     )
-                    self.nv_connections.add_connection(left=f'{host}', right=f'{ip_info.device}@{host}')
-                    self.nv_connections.add_connection(left=network, right=f'{ip_info.device}@{host}')
+                    self.nv_connections.add_connection(left=f'{host}', right=f'{device}@{host}')
+                    self.nv_connections.add_connection(left=network, right=f'{device}@{host}')
                 else:
                     self.nv_objects.add_ip_address(
                         host=host,
-                        interface=ip_info.device,
-                        raw_ip_address=ip_info.address,
+                        interface=device,
+                        raw_ip_address=interface_address.ip.compressed,
                         emblem=self.emblems.ip_address,
                     )
-                    self.nv_objects.add_interface(
-                        host=host, service=ip_info.device, host_cache=self.host_cache,
-                    )
+                    self.nv_objects.add_interface(host=host, service=device, item=item)
                     self.nv_connections.add_connection(
-                        left=host, right=f'{ip_info.device}@{host}')
+                        left=host, right=f'{device}@{host}')
                     self.nv_connections.add_connection(
-                        left=f'{ip_info.device}@{host}',
-                        right=f'{ip_info.address}@{ip_info.device}@{host}',
+                        left=f'{device}@{host}',
+                        right=f'{interface_address.ip.compressed}@{device}@{host}',
                     )
                     self.nv_connections.add_connection(
-                        left=network, right=f'{ip_info.address}@{ip_info.device}@{host}',
+                        left=network, right=f'{interface_address.ip.compressed}@{device}@{host}',
                     )
 
+    def get_network_summary(self, raw_ip_address: str) -> str | None:
+        for network in self.summarize:
+            try:
+                if ip_network(raw_ip_address).subnet_of(network):
+                    LOGGER.debug(f'{self.topology} IP address {raw_ip_address} is in subnet -> ({network})')
+                    return network.compressed
+            except TypeError:
+                pass
+        return None
+
+    def is_ignore_ip(self, raw_ip_address: str) -> bool:
+        for ip in self.ignore_ips:
+            try:
+                if ip_network(raw_ip_address).subnet_of(ip):
+                    LOGGER.debug(f'{self.topology} IP address {raw_ip_address} is in ignore list -> ({ip})')
+                    return True
+            except TypeError:
+                continue
+        return False
+
+    def is_ignore_wildcard(self, raw_ip_address: str) -> bool:
+        int_ip_address = int(ip_address(raw_ip_address))
+        for wildcard in self.ignore_wildcard:
+            if int_ip_address & wildcard.int_wildcard == wildcard.bit_pattern:
+                LOGGER.debug(
+                    f'{self.topology} IP address {raw_ip_address} matches ignore wildcard '
+                    f'list ({wildcard.ip_address}/{wildcard.wildcard})'
+                )
+                return True
+        return False
+
 
 def map_speed_to_thickness(speed_to_map: int, speed_map: Sequence[Thickness]) -> int:
     thickness: int = 1  # use in case of empty MAP_SPEED_TO_THICKNESS
@@ -823,173 +1003,6 @@ def map_speed_to_thickness(speed_to_map: int, speed_map: Sequence[Thickness]) ->
     return thickness
 
 
-def get_operational_interface_data(
-    host: str,
-    item: str,
-    host_cache: HostCache,
-
-) -> Dict[str, str | int] | None:
-    unit_to_bits_per_second = {
-        'Bit/s': 1,
-        'kBit/s': 1000,
-        'Kbps': 1000,
-        'MBit/s': 1000000,
-        'Mbps': 1000000,
-        'GBit/s': 1000000000,
-        'Gbps': 1000000000,
-    }
-
-    # get dict of interfaces with the item as key
-    interface_data: Dict[str, any] | None = host_cache.get_data(
-        host=host, item=CacheItems.interfaces, path=CACHE_INTERFACES_DATA
-    )
-    try:
-        raw_opdata = interface_data[item]['long_plugin_output']
-    except (KeyError, TypeError):
-        return None
-
-    if raw_opdata:
-        opdata: Dict[str, str | int] = {}
-        for _entry in raw_opdata:
-            try:
-                key, value = _entry.split(': ', 1)  # split only at the first colon
-            except ValueError:
-                continue
-            value = value.strip(' ')
-            match key:
-                case 'MAC':
-                    if len(value) == 17:  # valid MAC: 6C:DD:30:DD:51:8B'
-                        opdata['mac'] = value
-                case 'Speed':
-                    try:           # *_ -> ignore rest of string, i.e: (expected: 1 GBit/s)WARN
-                        speed, unit, *_ = value.split(' ')
-                    except ValueError:
-                        pass
-                    else:
-                        opdata['op_sped_str'] = f'{speed} {unit}'
-                        opdata['op_speed_int'] = int(float(speed) * unit_to_bits_per_second[unit])
-
-        return opdata
-    return None
-
-
-def get_service_by_interface(host: str, interface: str, host_cache: HostCache) -> str | None:
-    """
-    Returns:
-        Tuple of interface item
-    """
-
-    _alternate_if_name = {
-        'ethernet': 'eth',
-        # 'fastethernet': 'Fa',
-        # 'gigabitethernet': 'gi',
-        # 'tengigabitethernet': 'te',
-        # 'fortygigabitethernet': 'Fo',
-        # 'hundredgigabitethernet': 'Hu',
-        # 'management': 'Ma',
-    }
-
-    def _get_short_if_name(interface_: str) -> str:
-        """
-        returns short interface name from long interface name
-        interface: is the long interface name
-        :type interface_: str
-        """
-        if not interface_:
-            return interface_
-        for interface_prefix in _alternate_if_name.keys():
-            if interface_.lower().startswith(interface_prefix.lower()):
-                interface_short = _alternate_if_name[interface_prefix]
-                return interface_.lower().replace(interface_prefix.lower(), interface_short, 1)
-        return interface_
-
-    # try to find the item for an interface
-    def _match_entry_with_item(_entry: Mapping[str, str], services: Sequence[str]) -> str | None:
-        values = [
-            _entry.get('name'.strip()),
-            _entry.get('description'.strip()),
-            _entry.get('alias').strip()
-        ]
-        for value in values:
-            if value in services:
-                return value
-
-        index = str(_entry.get('index'))
-
-        # try alias+index
-        alias_index = str(_entry.get('alias')).strip() + ' ' + index
-        if alias_index in services:
-            LOGGER.info(f'match found by alias-index|{_entry}|{alias_index}|')
-            return alias_index
-
-        # try descrption+index
-        description_index = str(_entry.get('description')).strip() + ' ' + index
-        if description_index in services:
-            LOGGER.info(f'match found by alias-index|{_entry}|{description_index}|')
-            return description_index
-
-        # for index try with padding
-        pad_services: List[str] = [x for x in services if x.isdigit()]
-        if pad_services:
-            max_pad = len(max(pad_services, key=len)) + 1
-            min_pad = len(min(pad_services, key=len))
-            for i in range(min_pad, max_pad):
-                index_padded = f'{index:0>{i}}'
-                if index_padded in pad_services:
-                    return index_padded
-                # still not found try values + index
-                for value in values:
-                    if f'{value} {index_padded}' in services:
-                        return f'{value} {index_padded}'
-
-        LOGGER.warning(f'no match found |{_entry}|{services}|')
-        return None
-
-    # empty host/neighbour should never happen here
-    if not host:
-        LOGGER.warning(f'no host name |{host}|')
-        return None
-
-    # get dict of interfaces with the item as key
-    interface_data: Mapping[str, Mapping[str, object]] = host_cache.get_data(
-        host=host, item=CacheItems.interfaces, path=CACHE_INTERFACES_DATA
-    )
-    if not interface_data:
-        LOGGER.warning(f'got no interface data for: {host}')
-        return None
-
-    # try to find the interface in the host interface inventory list
-    inventory = host_cache.get_data(
-        host=host, item=CacheItems.inventory, path=PATH_INTERFACES
-    )
-    if not inventory:
-        LOGGER.warning(f'no interface inventory for host: {host}')
-        return None
-
-    interface_items: Sequence[str] = list(interface_data.keys())
-
-    # the easy case
-    if interface in interface_items:
-        return interface
-
-    for _entry in inventory:
-        if interface in [
-            _entry.get('name'),
-            _entry.get('description'),
-            _entry.get('alias'),
-            str(_entry.get('index')),
-            _entry.get('phys_address'),
-        ]:
-            return _match_entry_with_item(_entry, interface_items)
-        elif f'1:{interface}' == _entry.get('name'):  # Extreme non stack:
-            return _match_entry_with_item(_entry, interface_items)
-        elif _entry.get('name') is not None and _get_short_if_name(
-                _entry.get('name')) == str(interface).lower():  # Cisco NXOS
-            return _match_entry_with_item(_entry, interface_items)
-
-    LOGGER.warning(msg=f'Device: {host}: service for interface |{interface}| not found')
-
-
 def add_tooltip_html(
         metadata: Dict,
         type_: str,
@@ -1004,61 +1017,61 @@ def add_tooltip_html(
     if metadata['tooltip'].get('html') is None:
         css_style = (
             '<style>'
-                'div.mismatch {'
-                    'background-color: rgba(70,70,70,.1);'
-                    'border-radius: 5px;'
-                    'padding: 5px;'
-                '}'
-                'p.mismatch {'
-                    'text-align: center;'
-                '}'
-                'table.mismatch {'
-                    'text-align: left;'
-                    'border-radius: 5px;'
-                '}'
-                'tr.mismatch {'
-                '}'
-                'tr.mismatch:nth-child(even) {'
-                    'background-color: rgba(100,100,100,.3);'
-
-                '}'
-                'tr.mismatch:nth-child(odd) {'
-                    'background-color: rgba(100,100,100,.2);'
-
-                '}'
-                'th.mismatch {'
-                    'background-color: rgba(120,120,120,.3);'
-                    'padding: 5px;'   # inside the element
-                    # 'margin: 5px;'  # outside of the lement
-                '}'
-                'td.mismatch {'
-                    'padding: 5px;'
-                '}'
+            'div.mismatch {'
+            'background-color: rgba(70,70,70,.1);'
+            'border-radius: 5px;'
+            'padding: 5px;'
+            '}'
+            'p.mismatch {'
+            'text-align: center;'
+            '}'
+            'table.mismatch {'
+            'text-align: left;'
+            'border-radius: 5px;'
+            '}'
+            'tr.mismatch {'
+            '}'
+            'tr.mismatch:nth-child(even) {'
+            'background-color: rgba(100,100,100,.3);'
+
+            '}'
+            'tr.mismatch:nth-child(odd) {'
+            'background-color: rgba(100,100,100,.2);'
+
+            '}'
+            'th.mismatch {'
+            'background-color: rgba(120,120,120,.3);'
+            'padding: 5px;'  # inside the element
+            # 'margin: 5px;'  # outside of the lement
+            '}'
+            'td.mismatch {'
+            'padding: 5px;'
+            '}'
             '</style>'
         )
         header = (
             '<thead>'
-                '<tr>'
-                    f'<th {css_class}>Node</th>'
-                    f'<th {css_class}>{left}</th>'
-                    f'<th {css_class}>{right}</th>'
-                '</tr>'
+            '<tr>'
+            f'<th {css_class}>Node</th>'
+            f'<th {css_class}>{left}</th>'
+            f'<th {css_class}>{right}</th>'
+            '</tr>'
             '</thead>'
         )
         metadata['tooltip']['html'] = (
             f'{css_style}'
             f'<div {css_class}>'
-                f'<p {css_class}>WARNING: Mismatch found!</p>'
-                f'<table {css_class}>'
-                    f'{header}'
-                    '<tbody>'
+            f'<p {css_class}>WARNING: Mismatch found!</p>'
+            f'<table {css_class}>'
+            f'{header}'
+            '<tbody>'
         )
 
     metadata['tooltip']['html'] += (
         f'<tr {css_class}>'
-            f'<td {css_class}>{type_}</td>'
-            f'<td {css_class}>{left_value}</td>'
-            f'<td {css_class}>{right_value}</td>'
+        f'<td {css_class}>{type_}</td>'
+        f'<td {css_class}>{left_value}</td>'
+        f'<td {css_class}>{right_value}</td>'
         '</tr>'
     )
 
@@ -1066,45 +1079,6 @@ def add_tooltip_html(
 
 
 def close_tooltip_html(metadata: Dict) -> Dict:
-    metadata['tooltip']['html'] += '</table></div>'
+    if metadata.get('tooltip', {}).get('html'):
+        metadata['tooltip']['html'] += '</table></div>'
     return metadata
-
-
-def get_network_summary(raw_ip_address: str, summarize: Sequence[ip_network]) -> str | None:
-    for network in summarize:
-        try:
-            if ip_network(raw_ip_address).subnet_of(network):
-                return network.compressed
-        except TypeError:
-            pass
-    return None
-
-
-def is_ignore_ip(raw_ip_address: str, ignore_ips: Sequence[ip_network]) -> bool:
-    for ip in ignore_ips:
-        try:
-            if ip_network(raw_ip_address).subnet_of(ip):
-                LOGGER.info(f'IP address {raw_ip_address} is in ignore list -> ({ip})')
-                return True
-        except TypeError:
-            continue
-    return False
-
-
-def is_ignore_wildcard(raw_ip_address: str, ignore_wildcard: Sequence[Wildcard]) -> bool:
-    int_ip_address = int(ip_address(raw_ip_address))
-    for wildcard in ignore_wildcard:
-        if int_ip_address & wildcard.int_wildcard == wildcard.bit_pattern:
-            LOGGER.info(
-                f'IP address {raw_ip_address} matches ignore wildcard '
-                f'list ({wildcard.ip_address}/{wildcard.wildcard})'
-            )
-            return True
-    return False
-
-
-def get_list_of_devices(data: Mapping) -> List[str]:
-    devices: List[str] = []
-    for connection in data.values():
-        devices.append(connection[0])
-    return list(set(devices))
diff --git a/source/bin/nvdct/lib/utils.py b/source/bin/nvdct/lib/utils.py
index c504922..9d15149 100755
--- a/source/bin/nvdct/lib/utils.py
+++ b/source/bin/nvdct/lib/utils.py
@@ -8,45 +8,34 @@
 # File  : nvdct/lib/utils.py
 
 from ast import literal_eval
-from collections.abc import Mapping, Sequence
-from dataclasses import dataclass
-from json import dumps
+from collections.abc import Mapping, MutableSequence, Sequence
+from json import dumps, loads
 from logging import disable as log_off, Formatter, getLogger, StreamHandler
 from logging.handlers import RotatingFileHandler
 from pathlib import Path
-from re import match as re_match
+from re import match as re_match, findall as re_findall, sub as re_sub
 from socket import socket, AF_UNIX, AF_INET, SOCK_STREAM, SHUT_WR
 from sys import stdout, exit as sys_exit
 from time import time as now_time
 from tomllib import loads as toml_loads, TOMLDecodeError
-from typing import List, Dict, TextIO
+from typing import Dict, List, TextIO
 
 from lib.constants import (
+    Backends,
     CMK_SITE_CONF,
+    Case,
     DATAPATH,
+    EmblemValues,
+    EmblemNames,
     ExitCodes,
     LOGGER,
+    LogLevels,
     OMD_ROOT,
+    TomlSections,
+    TomlSettings,
 )
 
 
-@dataclass(frozen=True)
-class IpInfo:
-    address: str
-    device: str
-    broadcast: str
-    cidr: int
-    netmask: str
-    network: str
-    type: str
-    scope_id: str | None
-
-@dataclass(frozen=True)
-class InventoryColumns:
-    neighbour: str
-    local_port: str
-    neighbour_port: str
-
 def get_local_cmk_version() -> str:
     return Path(f'{OMD_ROOT}/version').readlink().name
 
@@ -64,7 +53,7 @@ def get_data_form_live_status(query: str) -> Dict | List | None:
     sock.connect(address)
     sock.sendall(query.encode())
     sock.shutdown(SHUT_WR)
-    chunks: List = []
+    chunks: MutableSequence = []
     while len(chunks) == 0 or chunks[-1] != "":
         chunks.append(sock.recv(4096).decode())
     sock.close()
@@ -112,13 +101,14 @@ def remove_old_data(keep: int, min_age: int, raw_path: str, protected: Sequence[
     path: Path = Path(raw_path)
     default_topo = path.joinpath('default')
     directories = [str(directory) for directory in list(path.iterdir())]
-    # keep default top
+
+    # keep default topo
     if str(default_topo) in directories:
         directories.remove(str(default_topo))
         keep -= 1
         if default_topo.is_symlink():
             try:
-                directories.remove(str(default_topo.readlink()))
+                directories.remove(str(path.joinpath(str(default_topo.readlink()))))
             except ValueError:
                 pass
 
@@ -140,80 +130,56 @@ def remove_old_data(keep: int, min_age: int, raw_path: str, protected: Sequence[
         if Path(directory).is_dir():
             topo_by_age[Path(directory).stat().st_ctime] = directory
 
-    topo_age = list(topo_by_age.keys())
-    topo_age.sort()
+    topo_age: List = list(topo_by_age.keys())
+    topo_age.sort(reverse=True)
 
     while len(topo_by_age) > keep:
-        if min_age * 86400 > now_time() - topo_age[0]:
+        entry = topo_age.pop()
+        if min_age * 86400 > now_time() - entry:
             LOGGER.info(
-                msg=f'Topology "{Path(topo_by_age[topo_age[0]]).name}'
+                msg=f'Topology "{Path(topo_by_age[entry]).name}'
                     f'" not older then {min_age} day(s). not deleted.'
             )
             return
-        LOGGER.info(f'delete old topology: {topo_by_age[topo_age[0]]}')
-        rm_tree(Path(topo_by_age[topo_age[0]]))
-        topo_by_age.pop(topo_age[0])
-        topo_age.pop(0)
+        LOGGER.info(f'delete old topology: {topo_by_age[entry]}')
+        rm_tree(Path(topo_by_age[entry]))
+        topo_by_age.pop(entry)
 
 
-def save_data_to_file(data: Mapping, path: str, file: str, make_default: bool) -> None:
+def save_data_to_file(
+        data: Mapping,
+        path: str,
+        file: str,
+        make_default: bool,
+        dont_compare: bool,
+) -> None:
     """
     Save the data as json file.
     Args:
         data: the topology data
-        path: the path were to save the dat
-        file: the file name to save  data in
+        path: the path inder DATATAPATH
+        file: the file name to save the data in
         make_default: if True, create the symlink "default" with path as target
-
+        dont_compare: if True, data will not be compared with default data
     Returns:
         None
     """
+    if not Path(f'{DATAPATH}/default').exists():
+        make_default = True
+    elif Path(f'{DATAPATH}/default/{file}').exists() and not dont_compare:
+        if is_equal_with_default(data, f'{DATAPATH}/default/{file}'):
+            LOGGER.warning(f'Data identical to default. Not saved! Use "--dont-compare".')
+            return
 
-    path_file = f'{path}/{file}'
+    path_file = f'{DATAPATH}/{path}/{file}'
     save_file = Path(f'{path_file}')
     save_file.parent.mkdir(exist_ok=True, parents=True)
     save_file.write_text(dumps(data))
 
-    parent_path = Path(f'{path}').parent
-    if not Path(f'{parent_path}/default').exists():
-        make_default = True
-    if make_default:
-        Path(f'{parent_path}/default').unlink(missing_ok=True)
-        Path(f'{parent_path}/default').symlink_to(target=Path(path), target_is_directory=True)
-
 
-# CMK version 2.2.x format
-def save_topology(
-        data: dict,
-        base_directory: str,
-        output_directory: str,
-        dont_compare: bool,
-        make_default: bool,
-        topology_file_name: str,
-) -> None:
-    path = f'{base_directory}/{output_directory}'
-
-    def _save():
-        save_data_to_file(
-            data=data,
-            path=path,
-            file=topology_file_name,
-            make_default=make_default,
-        )
-
-    if dont_compare:
-        _save()
-    else:
-        if not is_equal_with_default(
-                data=data,
-                file=f'{base_directory}/default/{topology_file_name}'
-        ):
-            _save()
-        else:
-            LOGGER.warning(
-                msg='Topology matches default topology, not saved! Use'
-                    '"--dont-compare" to save identical topologies.'
-            )
+    if make_default:
+        Path(f'{DATAPATH}/default').unlink(missing_ok=True)
+        Path(f'{DATAPATH}/default').symlink_to(target=Path(path), target_is_directory=True)
 
 
 def is_mac_address(mac_address: str) -> bool:
@@ -249,8 +215,11 @@ def is_list_of_str_equal(list1: List[str], list2: List[str]) -> bool:
     """
     tmp_list1 = list1.copy()
     tmp_list2 = list2.copy()
-    tmp_list1.sort()
-    tmp_list2.sort()
+    try:
+        tmp_list1.sort()
+        tmp_list2.sort()
+    except TypeError:  # list of dict cant be sorted (?)
+        pass
     return tmp_list1 == tmp_list2
 
 
@@ -262,6 +231,7 @@ def is_valid_hostname(host: str) -> bool:
         LOGGER.error(f'Invalid hostname found: {host}')
         return False
 
+
 def is_valid_site_name(site: str) -> bool:
     re_host_pattern = r'^[0-9a-z-A-Z\.\-\_]{1,16}$'
     if re_match(re_host_pattern, site):
@@ -270,6 +240,7 @@ def is_valid_site_name(site: str) -> bool:
         LOGGER.error(f'Invalid site name found: {site}')
         return False
 
+
 def is_valid_customer_name(customer: str) -> bool:
     re_host_pattern = r'^[0-9a-z-A-Z\.\-\_]{1,16}$'
     if re_match(re_host_pattern, customer):
@@ -288,6 +259,7 @@ def is_valid_output_directory(directory: str) -> bool:
         LOGGER.error(f'Invalid output directory name found: {directory}')
         return False
 
+
 def is_valid_log_file(log_file: str) -> bool:
     if not log_file.startswith(f'{OMD_ROOT}/var/log/'):
         LOGGER.error(f'Logg file needs to be under "{OMD_ROOT}/var/log/"! Got {Path(log_file).absolute()}')
@@ -295,32 +267,6 @@ def is_valid_log_file(log_file: str) -> bool:
     return True
 
 
-# not used in cmk 2.3.x format
-def merge_topologies(topo_pri: Dict, topo_sec: Dict) -> Dict:
-    """
-    Merge dict_prim into dict_sec
-    Args:
-        topo_pri: data of dict_pri will overwrite the data in dict_sec
-        topo_sec: dict where the data of dict_pri will be merged to
-
-    Returns:
-        Dict: topo_sec that contains merged data from top_sec and top_pri
-    """
-    keys_pri = list(topo_pri.keys())
-
-    # first transfer all completely missing items from dict_prim to dict_sec
-    for key in keys_pri:
-        if key not in topo_sec.keys():
-            topo_sec[key] = topo_pri[key]
-        else:
-            topo_sec[key]['connections'].update(topo_pri[key].get('connections', {}))
-            topo_sec[key]['interfaces'] = list(
-                set((topo_sec[key]['interfaces'] + topo_pri[key].get('interfaces', [])))
-            )
-        topo_pri.pop(key)
-    return topo_sec
-
-
 def compare_dicts(dict1: Mapping, dict2: Mapping) -> bool:
     # check top level keys
     if not is_list_of_str_equal(list(dict1.keys()), list(dict2.keys())):
@@ -329,23 +275,35 @@ def compare_dicts(dict1: Mapping, dict2: Mapping) -> bool:
         LOGGER.debug(f'dict1: {list(dict2.keys())}')
         return False
 
+    LOGGER.debug('Top level matches')
     for key, value in dict1.items():
-        _type = type(value)
-        if _type == dict:
+        type_ = type(value)
+        if type_ == dict:
+            LOGGER.debug(f'compare dict: {key}')
             if not compare_dicts(value, dict2[key]):
                 return False
-        elif _type == list:
+        elif type_ == list:
             if not is_list_of_str_equal(value, dict2[key]):
                 LOGGER.debug(f'list1: {value}')
                 LOGGER.debug(f'list2: {dict2[key]}')
                 return False
-        elif _type == str:
+        elif type_ in [str, int]:
+            if not value == dict2[key]:
+                LOGGER.debug('value dont match')
+                LOGGER.debug(f'value1: {value}')
+                LOGGER.debug(f'value2: {dict2[key]}')
+                return False
+        elif value is None:
             if not value == dict2[key]:
                 LOGGER.debug('value dont match')
                 LOGGER.debug(f'value1: {value}')
-                LOGGER.debug(f'value2 {dict2[key]}')
+                LOGGER.debug(f'value2: {dict2[key]}')
                 return False
         else:
+            LOGGER.debug(f'Compare unknown type {type_}')
+            LOGGER.debug(f'key: {key}')
+            LOGGER.debug(f'value1: {value}')
+            LOGGER.debug(f'value2: {dict2[key]}')
             return False
 
     return True
@@ -354,10 +312,26 @@ def compare_dicts(dict1: Mapping, dict2: Mapping) -> bool:
 def is_equal_with_default(data: Mapping, file: str) -> bool:
     default_file = Path(file)
     if default_file.exists():
-        default_data = literal_eval(default_file.read_text())
+        LOGGER.info(f'compare data with {file}')
+        default_data = loads(default_file.read_text())
         return compare_dicts(data, default_data)
     return False
 
+def get_attributes_from_inventory(inventory: Dict[str, object], raw_path: str):
+    # print(inventory['Nodes']['networking']['Nodes']['lldp_cache']['Attributes']['Pairs'])
+    path: List[str] = ('Nodes,' + ',Nodes,'.join(raw_path.split(',')) + ',Attributes,Pairs').split(',')
+    try:
+        table = inventory.copy()
+    except AttributeError:
+        return None
+    for m in path:
+        try:
+            table = table[m]
+        except KeyError:
+            LOGGER.info(msg=f'Inventory attributes for {path} not found')
+            return None
+    return dict(table)
+
 
 def get_table_from_inventory(inventory: Dict[str, object], raw_path: str) -> List | None:
     path: List[str] = ('Nodes,' + ',Nodes,'.join(raw_path.split(',')) + ',Table,Rows').split(',')
@@ -422,3 +396,66 @@ class StdoutQuiet:
 
     def flush(self):
         self._org_stdout.flush()
+
+
+def adjust_toml(toml_file: str):
+    fix_options = {
+        'DROP_HOSTS': TomlSections.L2_DROP_NEIGHBOURS,
+        'HOST_MAP': TomlSections.L2_HOST_MAP,
+        'L2_DROP_HOSTS': TomlSections.L2_DROP_NEIGHBOURS,  # needs to be before DROP_HOST
+        'L3V4_IGNORE_HOSTS': TomlSections.L3_IGNORE_HOSTS,
+        'L3V4_IGNORE_IP': TomlSections.L3_IGNORE_IP,
+        'L3V4_IRNORE_WILDCARD': TomlSections.L3V4_IGNORE_WILDCARD,
+        'L3V4_REPLACE': TomlSections.L3_REPLACE,
+        'L3V3_REPLACE': TomlSections.L3_REPLACE,
+        'L3V4_SUMMARIZE': TomlSections.L3_SUMMARIZE,
+        'SEED_DEVICES': TomlSections.L2_SEED_DEVICES,
+        'icon_missinc': EmblemValues.ICON_ALERT_UNREACHABLE,
+        'icon_missing': EmblemValues.ICON_ALERT_UNREACHABLE,
+        'l3v4_replace': EmblemNames.L3_REPLACE,
+        'l3v4_summarize': EmblemNames.L3_SUMMARIZE,
+        'keep_domain = true': f'{TomlSettings.REMOVE_DOMAIN} = false',
+        'keep_domain = false': f'{TomlSettings.REMOVE_DOMAIN} = true',
+    }
+    old_options = {
+        'lowercase': f'{TomlSettings.CASE} = {Case.LOWER}',
+        'uppercase': f'{TomlSettings.CASE} = {Case.UPPER}',
+        f'FILESYSTEM': {Backends.MULTISITE},
+        'debug': f'{TomlSettings.LOG_LEVEL} = {LogLevels.DEBUG}',
+        'keep_domain': f'{TomlSettings.REMOVE_DOMAIN} = true/false'
+    }
+    changed: bool = False
+    org_file = Path(toml_file)
+    if org_file.exists():
+        print(f'Checking file.: {org_file.name}')
+        org_content: str = org_file.read_text()
+        content: str = org_content
+        for old, new in fix_options.items():
+            re_pattern = f'\\b{old}\\b'
+            count = len(re_findall(re_pattern, org_content))
+            if count > 0:
+                changed = True
+                content = re_sub(re_pattern, new, content)
+                print(f'Found value...: "{old}" {count} times, replaced by "{new}"')
+
+        for old, new in old_options.items():
+            re_pattern = f'\\b{old}\\b'
+            count = len(re_findall(re_pattern, org_content))
+            if count > 0:
+                print(f'Obsolete......: "{old}", use "{new}" instead')
+
+        if changed:
+            backup_file = Path(f'{toml_file}.backup')
+            if not backup_file.exists():
+                org_file.rename(backup_file)
+                print(f'Renamed TOML..: {backup_file.name}')
+                new_file = Path(toml_file)
+                new_file.open('w').write(content)
+                print(f'Written fixed.: {new_file.name}')
+            else:
+                print(
+                    f'Can not create backup file {backup_file.name}, file exists. Aborting!\n'
+                    f'Nothing has changed.'
+                )
+        else:
+            print('Finished......: Nothing found to fix.')
diff --git a/source/bin/nvdct/nvdct.py b/source/bin/nvdct/nvdct.py
index ffd8468..8d93b27 100755
--- a/source/bin/nvdct/nvdct.py
+++ b/source/bin/nvdct/nvdct.py
@@ -157,7 +157,23 @@
 #                 [EMBLEMS]
 #                 l3v4_replace         -> l3_replace
 #                 l3v4_summarize       -> l3_summarize
+# 2024-12-25: fixed "--dont-compare", data will only be saved if the are different from the default
+#             changed: is seed devices is not configured use all CDP/LLDP devices (by host label)
+# 2024-12-26: INCOMPATIBLE: renamed L2_DROP_HOSTS -> L2_DROP_NEIGHBOURS
+#             added option --display-l2-neighbours
+# 2024-12-27: added options
+#               --adjust-toml
+#               --include-l3-loopback
+#               --skip-l3-cidr-0
+#               --skip-l3-cidr-32-128
+#               --skip-l3-public
+#             fixed: keep default topology
+#             fixed: cleanup -> remove the oldest topologies not the newest
+#             INCOMPATIBLE: removed: CUSTOM_LAYERS
+#             refactoring constants
+#
 
+#
 # creating topology data json from inventory data
 #
 # This script creates the topology data file needed for the Checkmk "network_visualization" plugin
@@ -254,7 +270,10 @@ __data = {
 """
 
 import sys
+
+from collections.abc import MutableSequence
 from time import strftime, time_ns
+
 from typing import List
 
 from lib.args import parse_arguments
@@ -265,14 +284,17 @@ from lib.backends import (
     HostCacheRestApi,
 )
 from lib.constants import (
+    Backends,
     DATAPATH,
-    HOME_URL,
+    URLs,
+    HostLabels,
     IPVersion,
-    LABEL_L3v4,
-    LAYERS,
+    InvPaths,
     LOGGER,
-    Layer,
+    Layers,
     NVDCT_VERSION,
+    TomlSections,
+    TomlSettings,
 )
 from lib.settings import Settings
 from lib.topologies import (
@@ -282,8 +304,8 @@ from lib.topologies import (
 )
 from lib.utils import (
     ExitCodes,
-    InventoryColumns,
     StdoutQuiet,
+    adjust_toml,
     configure_logger,
     remove_old_data,
 )
@@ -307,20 +329,29 @@ def main():
     print(
         f'Network Visualisation Data Creation Tool (NVDCT)\n'
         f'by thl-cmk[at]outlook[dot]com, version {NVDCT_VERSION}\n'
-        f'see {HOME_URL}'
+        f'see {URLs.NVDCT}'
     )
     print('')
     print(f'Start time....: {strftime(settings.time_format)}')
 
+    if settings.fix_toml:
+        adjust_toml(settings.user_data_file)
+        print(f'Time taken....: {(time_ns() - start_time) / 1e9}/s')
+        print(f'End time......: {strftime(settings.time_format)}')
+        print('')
+
+        LOGGER.critical('Data creation finished')
+        sys.exit()
+
     match settings.backend:
-        case 'RESTAPI':
+        case Backends.RESTAPI:
             host_cache: HostCache = HostCacheRestApi(
                 pre_fetch=settings.pre_fetch,
                 api_port=settings.api_port,
                 filter_sites=settings.filter_sites,
-                sites=settings.sites
+                sites=settings.sites,
             )
-        case 'MULTISITE':
+        case Backends.MULTISITE:
             host_cache: HostCache = HostCacheMultiSite(
                 pre_fetch=settings.pre_fetch,
                 filter_sites=settings.filter_sites,
@@ -328,42 +359,47 @@ def main():
                 filter_customers=settings.filter_customers,
                 customers=settings.customers,
             )
-        case 'LIVESTATUS':
+        case Backends.LIVESTATUS:
             host_cache: HostCache = HostCacheLiveStatus(
                 pre_fetch=settings.pre_fetch,
             )
         case _:
-            LOGGER.error(msg=f'Backend {settings.backend} not (yet) implemented')
+            LOGGER.error(msg=f'Backend {settings.backend} not implemented')
             host_cache: HostCache | None = None  # to keep linter happy
             sys.exit(ExitCodes.BACKEND_NOT_IMPLEMENTED)
 
-    jobs: List[Layer] = []
+    host_cache.init_neighbour_to_host(
+        case=settings.case,
+        l2_host_map=settings.l2_host_map,
+        prefix=settings.prefix,
+        remove_domain=settings.remove_domain,
+    )
+
+    jobs: MutableSequence = []
     pre_fetch_layers: List[str] = []
     pre_fetch_host_list: List[str] = []
 
     for layer in settings.layers:
         match layer:
-            case 'STATIC':
+            case Layers.STATIC:
+                jobs.append(layer)
+            case Layers.L3V4:
                 jobs.append(layer)
-            case 'L3v4':
+                host_cache.add_inventory_path(path=InvPaths.L3)
+                pre_fetch_layers.append(HostLabels.L3V4_ROUTER)
+            case Layers.CDP | Layers.LLDP:
                 jobs.append(layer)
-                host_cache.add_inventory_path(path=LAYERS[layer].path)
-                pre_fetch_layers.append(LAYERS[layer].host_label)
-            case 'CUSTOM':
-                for entry in settings.custom_layers:
-                    jobs.append(entry)
-                    host_cache.add_inventory_path(entry.path)
-            case 'CDP' | 'LLDP':
-                jobs.append(LAYERS[layer])
-                host_cache.add_inventory_path(path=LAYERS[layer].path)
-                pre_fetch_layers.append(LAYERS[layer].host_label)
+                host_cache.add_inventory_path(InvPaths.CDP if layer == Layers.CDP else InvPaths.LLDP)
+                pre_fetch_layers.append(HostLabels.CDP if layer == Layers.CDP else HostLabels.LLDP)
             case _:
                 LOGGER.warning(f'Unknown layer {layer} dropped.')
                 continue
 
     if not jobs:
-        message = ('No layer to work on. Please configura at least one layer (i.e. CLI option "-l CDP")\n'
-                   'See ~/local/bin/nvdct/conf/nvdct.toml -> SETTINGS -> layers')
+        message = (
+            f'No layer to work on. Please configura at least one layer (i.e. CLI option "-l {Layers.CDP}")\n'
+            f'See {settings.user_data_file} -> {TomlSections.SETTINGS} -> {TomlSettings.LAYERS}'
+        )
         LOGGER.warning(message)
         print(message)
         sys.exit(ExitCodes.NO_LAYER_CONFIGURED)
@@ -371,8 +407,8 @@ def main():
     if settings.pre_fetch:
         LOGGER.info('Pre fill cache...')
         for host_label in pre_fetch_layers:
-            if _host_list := host_cache.get_hosts_by_label(host_label):
-                pre_fetch_host_list = list(set(pre_fetch_host_list + _host_list))
+            if host_list := host_cache.get_hosts_by_label(host_label):
+                pre_fetch_host_list = list(set(pre_fetch_host_list + list(host_list)))
         LOGGER.info(f'Fetching data for {len(pre_fetch_host_list)} hosts start')
         print(f'Prefetch start: {strftime(settings.time_format)}')
         print(f'Prefetch hosts: {len(pre_fetch_host_list)} of {len(host_cache.cache)}')
@@ -382,16 +418,14 @@ def main():
 
     for job in jobs:
         match job:
-            case 'STATIC':
+            case Layers.STATIC:
                 label = job
                 topology = TopologyStatic(
                     connections=settings.static_connections,
                     emblems=settings.emblems,
                     host_cache=host_cache,
                 )
-                topology.create()
-
-            case 'L3v4':
+            case Layers.L3V4:
                 label = job
                 topology = TopologyL3(
                     emblems=settings.emblems,
@@ -401,37 +435,42 @@ def main():
                     ignore_wildcard=settings.l3v4_ignore_wildcard,
                     include_hosts=settings.include_l3_hosts,
                     replace=settings.l3_replace,
+                    skip_cidr_0=settings.skip_l3_cidr_0,
+                    skip_cidr_32_128=settings.skip_l3_cidr_32_128,
                     skip_if=settings.skip_l3_if,
                     skip_ip=settings.skip_l3_ip,
+                    skip_public=settings.skip_l3_public,
+                    include_loopback=settings.include_l3_loopback,
                     summarize=settings.l3_summarize,
-                    version=IPVersion.IPv4 if job == LABEL_L3v4 else IPVersion.IPv6
+                    version=IPVersion.IPv4 if job == Layers.L3V4 else IPVersion.IPv6
                 )
-                topology.create()
-
-            case _:
-                label = job.label.upper()
-                columns = job.columns.split(',')
+            case Layers.CDP | Layers.LLDP:
+                label = job
+                if job == Layers.CDP:
+                    host_label = HostLabels.CDP
+                    inv_path = InvPaths.CDP
+                else:
+                    host_label = HostLabels.LLDP
+                    inv_path = InvPaths.LLDP
+                if not (seed_devices := settings.l2_seed_devices):
+                    seed_devices = host_cache.get_hosts_by_label(host_label)
                 topology = TopologyL2(
-                    case=settings.case,
                     emblems=settings.emblems,
                     host_cache=host_cache,
-                    inv_columns=InventoryColumns(
-                        neighbour=columns[0],
-                        local_port=columns[1],
-                        neighbour_port=columns[2]
-                    ),
-                    l2_drop_hosts=settings.l2_drop_hosts,
-                    l2_host_map=settings.l2_host_map,
+                    l2_drop_neighbours=settings.l2_drop_neighbours,
                     l2_neighbour_replace_regex=settings.l2_neighbour_replace_regex,
                     label=label,
-                    path_in_inventory=job.path,
-                    prefix=settings.prefix,
-                    remove_domain=settings.remove_domain,
-                    seed_devices=settings.l2_seed_devices,
+                    path_in_inventory=inv_path,
+                    seed_devices=seed_devices,
+                    display_l2_neighbours=settings.display_l2_neighbours,
                 )
-                topology.create()
-
+            case _:
+                LOGGER.warning(f'Unknown layer {job}, ignoring.')
+                continue
+        pre_message = f'Layer {label:.<8s}: '
+        print(pre_message, end ='', flush=True)
 
+        topology.create()
         topology.nv_connections.add_meta_data_to_connections(
             nv_objects=topology.nv_objects,
             speed_map=settings.map_speed_to_thickness,
@@ -440,14 +479,15 @@ def main():
         topology.save(
             label=label,
             output_directory=settings.output_directory,
-            make_default=settings.default
+            make_default=settings.default,
+            dont_compare=settings.dont_compare,
         )
 
         message = (
-            f'Layer {label:.<8s}: Devices/Objects/Connections added {topology.nv_objects.host_count}/'
+            f'Devices/Objects/Connections added {topology.nv_objects.host_count}/'
             f'{len(topology.nv_objects.nv_objects)}/{len(topology.nv_connections.nv_connections)}'
         )
-        LOGGER.info(msg=message)
+        LOGGER.info(msg=f'{pre_message} {message}')
         print(message)
 
     if settings.keep:
diff --git a/source/packages/nvdct b/source/packages/nvdct
index 329c316..32ad046 100644
--- a/source/packages/nvdct
+++ b/source/packages/nvdct
@@ -47,7 +47,7 @@
                    'htdocs/images/icons/location_80.png']},
  'name': 'nvdct',
  'title': 'Network Visualization Data Creation Tool (NVDCT)',
- 'version': '0.9.6-20241222',
+ 'version': '0.9.7-20241230',
  'version.min_required': '2.3.0b1',
  'version.packaged': 'cmk-mkp-tool 0.2.0',
  'version.usable_until': '2.4.0p1'}
-- 
GitLab