From 27104d7c2db285bc7c480b63aa9bca62e2f0fed4 Mon Sep 17 00:00:00 2001
From: "th.l" <thl-cmk@outlook.com>
Date: Wed, 11 Dec 2024 21:25:41 +0100
Subject: [PATCH] added option --include-l3-hosts, changed default layers to
 None, reworked static topology

---
 README.md                          |   2 +-
 mkp/nvdct-0.9.4-20241210.mkp       | Bin 0 -> 42490 bytes
 source/bin/nvdct/conf/nvdct.toml   |  49 ++-
 source/bin/nvdct/lib/args.py       |  29 +-
 source/bin/nvdct/lib/backends.py   |  86 +++--
 source/bin/nvdct/lib/constants.py  |  48 +++
 source/bin/nvdct/lib/settings.py   | 144 ++++----
 source/bin/nvdct/lib/topologies.py | 480 +++++++++++++++++++++++-
 source/bin/nvdct/lib/utils.py      | 118 +++---
 source/bin/nvdct/nvdct.py          | 574 +++++++----------------------
 source/packages/nvdct              |  17 +-
 11 files changed, 899 insertions(+), 648 deletions(-)
 create mode 100644 mkp/nvdct-0.9.4-20241210.mkp
 create mode 100755 source/bin/nvdct/lib/constants.py

diff --git a/README.md b/README.md
index 35905d2..274747b 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-[PACKAGE]: ../../raw/master/mkp/nvdct-0.9.3-20241209.mkp "nvdct-0.9.3-20241209.mkp"
+[PACKAGE]: ../../raw/master/mkp/nvdct-0.9.4-20241210.mkp "nvdct-0.9.4-20241210.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.4-20241210.mkp b/mkp/nvdct-0.9.4-20241210.mkp
new file mode 100644
index 0000000000000000000000000000000000000000..f1bbfad565be60d5a472b0fba691807e167707c0
GIT binary patch
literal 42490
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
V|JRco3SfE`JyDXfdpC#p?tkgLtuX)q

literal 0
HcmV?d00001

diff --git a/source/bin/nvdct/conf/nvdct.toml b/source/bin/nvdct/conf/nvdct.toml
index b5632b5..414861a 100755
--- a/source/bin/nvdct/conf/nvdct.toml
+++ b/source/bin/nvdct/conf/nvdct.toml
@@ -13,6 +13,7 @@
 #
 
 # list of (additional to -s/--seed-devices) seed devices
+# [0-9-a-zA-Z\.\_\-]{1,253} -> host
 L2_SEED_DEVICES = [
     # "CORE01",
     # "LOCATION01",
@@ -26,6 +27,7 @@ L2_DROP_HOSTS = [
 ]
 
 # hosts will be ignored in L3v4 topology
+# [0-9-a-zA-Z\.\_\-]{1,253} -> host
 L3V4_IGNORE_HOSTS = [
     # "host1",
     # "host2",
@@ -64,29 +66,39 @@ PROTECTED_TOPOLOGIES = [
 ]
 
 # user defined static connections
-# these connections will be added from host to neighbour and in reverese
-# hosts/neighbours in this section will be added to SEED_DEVICES
+# [0-9-a-zA-Z\.\_\-]{1,253} -> host
 STATIC_CONNECTIONS = [
-    # ["cmk_host1", "local-port1", "neighbour-port1", "neighbour1", "label"],
-    # ["cmk_host1", "local-port2", "neighbour-port2", "neighbour2", "label"],
+    # valid entry formats
+    # ["left_host", "left_service", "right_service", "right_host"],
+    # connection: "left_host"<->"left_service"<->"right_service"<->"right_host"
+    # ["left_host", "", "right_service", "right_host"],
+    # connection: "left_host"<->"right_service"<->"right_host"
+    # ["left_host", "left_service", "", "right_host"],
+    # connection: "left_host"<->"left_service"<->"right_host"
+    # ["left_host", "", "", "right_host"],
+    # connection: "left_host"<->"right_host"
 ]
 
-# optional custom layers use option -l/--layers CUSTOM to include this layers
+# 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 so include/excluse, use option --filter-costumers INCLUDE/EXCLUDE
+# list customers to include/excluse, use option --filter-costumers INCLUDE/EXCLUDE
+# [0-9-a-zA-Z\.\_\-]{1,16} -> customer
 CUSTOMERS = [
     # "customer1",
     # "customer2",
     # "customer3",
 ]
 
-# list site so include/excluse, use option --filter-sites INCLUDE/EXCLUDE
+# list site to include/excluse, use option --filter-sites INCLUDE/EXCLUDE
+# [0-9-a-zA-Z\.\_\-]{1,16} -> site
 SITES = [
     # "site1",
     # "site2",
@@ -94,6 +106,7 @@ SITES = [
 ]
 
 # map inventory 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"
@@ -106,6 +119,7 @@ SITES = [
 # "^Meraki.*\\s-\\s" = ""
 
 # replace network objects (takes place after summarize)
+# [0-9-a-zA-Z\.\_\-]{1,253} -> host
 [L3V4_REPLACE]
 # "10.193.172.0/24" = "MPLS"
 # "10.194.8.0/23" = "MPLS"
@@ -127,29 +141,30 @@ SITES = [
 # must be sorted from slower to faster speed
 # use only one 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
-# 51e7 = 4     # 51 mbit
-1e8 = 1  # 100 mbit
-1e9 = 3  # 1 gbit
-1e10 = 5 # 10 gbit
+# "2000000" = 1  # 2 mbit
+# "5000000" = 2  # 5 mbit
+# "1e7" = 3      # 10 mbit
+# "51e7" = 4     # 51 mbit
+"1e8" = 1  # 100 mbit
+"1e9" = 3  # 1 gbit
+"1e10" = 5 # 10 gbit
 
 [SETTINGS]
-# api_port = 80
+# api_port = 5001
 # backend = "MULTISITE" | "RESTAPI" | "LIVESTATUS"
 # case = "LOWER" | "UPPER"
 # default = false
 # dont_compare = false
 # filter_customers = "INCLUDE" |"EXCLUDE"
 # filter_sites = "INCLUDE" | "EXCLUDE"
+# include_l3_hosts = false
 # keep = 0
-# layers = ["LLDP", "CDP", "STATIC", "CUSTOM", "L3v4"]
+# layers = ["LLDP", "CDP", L3v4, "STATIC", "CUSTOM"]
 # log_file = "~/var/log/nvdct.log"
 # log_level = "WARNING"
 # log_to_stdout = false
 # min_age = 0
-# output_directory = ''
+# output_directory = ''  #
 # pre_fetch = false
 # prefix = ""
 # quiet = true
diff --git a/source/bin/nvdct/lib/args.py b/source/bin/nvdct/lib/args.py
index d113171..cf77c81 100755
--- a/source/bin/nvdct/lib/args.py
+++ b/source/bin/nvdct/lib/args.py
@@ -23,6 +23,7 @@
 # --dont-compare
 # --filter-customers
 # --filter-sites
+# --include-l3-hosts
 # --keep
 # --log-file
 # --log-level
@@ -43,18 +44,19 @@ from argparse import (
 )
 from pathlib import Path
 
-from lib.utils import (
-    ExitCodes,
+from lib.constants import (
     HOME_URL,
     MIN_CDP_VERSION,
+    MIN_LINUX_IP_ADDRESSES,
+    MIN_SNMP_IP_ADDRESSES,
+    MIN_WINDOWS_IP_ADDRESSES,
     MIN_LLDP_VERSION,
-    MIN_IP_ADDRESSES,
     NVDCT_VERSION,
-    # SAMPLE_SEEDS,
     SCRIPT,
     TIME_FORMAT_ARGPARSER,
     USER_DATA_FILE,
 )
+from lib.utils import ExitCodes
 
 
 def parse_arguments() -> arg_Namespace:
@@ -83,7 +85,7 @@ def parse_arguments() -> arg_Namespace:
                f' {ExitCodes.BACKEND_NOT_IMPLEMENTED.value} - Backend not implemented\n'
                f' {ExitCodes.AUTOMATION_SECRET_NOT_FOUND.value} - Automation secret not found\n'
                '\nUsage:\n'
-               f'{SCRIPT} -u ~/local/bin/nvdct/conf/{USER_DATA_FILE} \n\n'
+               f'{SCRIPT} -u ~/local/bin/nvdct/conf/my_{USER_DATA_FILE} \n\n'
     )
 
     parser.add_argument(
@@ -122,12 +124,13 @@ def parse_arguments() -> arg_Namespace:
         choices=['CDP', 'CUSTOM', 'LLDP', 'STATIC', 'L3v4'],
         # 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_IP_ADDRESSES}\n'
-            f'          adds, layer 3 topology fpr IPv4\n'
-            f' - STATIC (deprecated)\n'
-            f' - CUSTOM (deprecated)\n'
+            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'
         )
     )
     parser.add_argument(
@@ -192,6 +195,10 @@ def parse_arguments() -> arg_Namespace:
         help='INCLUDE/EXCLUDE site list from config file.\n'
              'Note: MULTISITE backend only.',
     )
+    parser.add_argument(
+        '--include-l3-hosts', action='store_const', const=True,  # default=False,
+        help='Include hosts (single IP objects) in layer 3 topology',
+    )
     parser.add_argument(
         '--remove-domain', action='store_const', const=True,  # default=False,
         help='Remove the domain name from the neighbor name',
diff --git a/source/bin/nvdct/lib/backends.py b/source/bin/nvdct/lib/backends.py
index 611b7a6..64cce3a 100755
--- a/source/bin/nvdct/lib/backends.py
+++ b/source/bin/nvdct/lib/backends.py
@@ -23,14 +23,17 @@ from sys import exit as sys_exit
 
 from livestatus import MultiSiteConnection, SiteConfigurations, SiteId
 
-from lib.utils import (
+from lib.constants import (
     CACHE_INTERFACES_DATA,
+    OMD_ROOT,
+    PATH_INTERFACES,
+)
+from lib.utils import (
     ExitCodes,
     get_data_form_live_status,
     get_table_from_inventory,
     LOGGER,
-    OMD_ROOT,
-    PATH_INTERFACES,
+
 )
 
 
@@ -410,8 +413,8 @@ class HostCacheMultiSite(HostCacheLiveStatus):
                 'local site only. Try -b RESTAPI if you have a distributed environment.'
             )
 
-    def filter_sites(self, filter: str| None, sites:List[str]):
-        match filter:
+    def filter_sites(self, filter_: str | None, sites: List[str]):
+        match filter_:
             case 'INCLUDE':
                 self.sites = {site: data for site, data in self.sites.items() if site in sites}
             case 'EXCLUDE':
@@ -419,8 +422,8 @@ class HostCacheMultiSite(HostCacheLiveStatus):
             case _:
                 return
 
-    def filter_costumers(self, filter: str | None, costumers:List[str]):
-        match filter:
+    def filter_costumers(self, filter_: str | None, costumers: List[str]):
+        match filter_:
             case 'INCLUDE':
                 self.sites = {
                     site: data for site, data in self.sites.items() if data.get('customer') in costumers
@@ -437,10 +440,17 @@ class HostCacheMultiSite(HostCacheLiveStatus):
 
 
 class HostCacheRestApi(HostCache):
-    def __init__(self, pre_fetch: bool, api_port: int):
+    def __init__(
+            self,
+            pre_fetch: bool,
+            api_port: int,
+            filter_sites: str | None = None,
+            sites: List[str] = [],
+    ):
         super().__init__(pre_fetch, '[RESTAPI]')
         LOGGER.debug(f'{self.backend} init backend')
         self._api_port = api_port
+        self.sites = []
         try:
             self.__secret = Path(
                 f'{OMD_ROOT}/var/check_mk/web/automation/automation.secret'
@@ -458,9 +468,33 @@ class HostCacheRestApi(HostCache):
         self.__session.headers['Authorization'] = f"Bearer {self.__user} {self.__secret}"
         self.__session.headers['Accept'] = 'application/json'
 
+        self.get_sites()
+        self.filter_sites(filter_=filter_sites, sites=sites)
+        LOGGER.info(f'{self.backend} filtered sites : {self.sites}')
+
         if self.pre_fetch:
             self.pre_fetch_hosts()
 
+    def get_sites(self):
+        LOGGER.debug(f'{self.backend} get_sites')
+        resp = self.__session.get(url=f"{self.__api_url}/domain-types/site_connection/collections/all")
+        if resp.status_code == 200:
+            sites = resp.json().get("value")
+            self.sites = [site.get('id') for site in sites]
+            LOGGER.debug(f'{self.backend} sites : {self.sites}')
+        else:
+            LOGGER.warning(f'{self.backend} got no site information! status code {resp.status_code}')
+            LOGGER.debug(f'{self.backend} response text: {resp.text}')
+
+    def filter_sites(self, filter_: str | None, sites: List[str]):
+        match filter_:
+            case 'INCLUDE':
+                self.sites = [site for site in self.sites if site in sites]
+            case 'EXCLUDE':
+                self.sites = [site for site in self.sites if site not in sites]
+            case _:
+                return
+
     def get_inventory_data(self, hosts: List[str]) -> Dict[str, Dict | None]:
         LOGGER.debug(f'{self.backend} get_inventory_data {hosts}')
         host_data: Dict[str, Dict | None] = {}
@@ -475,8 +509,9 @@ class HostCacheRestApi(HostCache):
             resp = self.__session.get(
                 url=f"{self.__api_url}/domain-types/host/collections/all",
                 params={
-                    "query": query,
-                    "columns": ['name', 'mk_inventory'],
+                    'query': query,
+                    'columns': ['name', 'mk_inventory'],
+                    'sites': self.sites,
                 },
             )
             if resp.status_code == 200:
@@ -487,7 +522,7 @@ class HostCacheRestApi(HostCache):
                     if host:
                         host_data[host] = raw_host['extensions'].get('mk_inventory')
             else:
-                LOGGER.info(
+                LOGGER.warning(
                     f'{self.backend} got no inventory data found!, status code {resp.status_code}'
                 )
                 LOGGER.debug(f'{self.backend} response query: {query}')
@@ -511,10 +546,11 @@ class HostCacheRestApi(HostCache):
             query = f'{{"op": "and", "expr": [{query_item},{query_host}]}}'
 
             resp = self.__session.get(
-                url=f"{self.__api_url}/domain-types/service/collections/all",
+                url=f'{self.__api_url}/domain-types/service/collections/all',
                 params={
-                    "query": query,
-                    "columns": ['host_name', 'description', 'long_plugin_output'],
+                    'query': query,
+                    'columns': ['host_name', 'description', 'long_plugin_output'],
+                    'sites': self.sites,
                 },
             )
 
@@ -556,10 +592,11 @@ class HostCacheRestApi(HostCache):
         #     return False
         query = '{"op": "=", "left": "name", "right": "' + host + '"}'
         resp = self.__session.get(
-                url=f"{self.__api_url}/domain-types/host/collections/all",
+                url=f'{self.__api_url}/domain-types/host/collections/all',
                 params={
-                    "query": query,
-                    "columns": ['name'],
+                    'query': query,
+                    'columns': ['name'],
+                    'sites': self.sites,
                 },
             )
         if resp.status_code == 200:
@@ -579,14 +616,14 @@ class HostCacheRestApi(HostCache):
 
     def get_hosts_by_label(self, label: str) -> List[str] | None:
         LOGGER.debug(f'{self.backend} get_hosts_by_label {label}')
-        # query = '{"op": "=", "left": "label_names", "right": "' + label + '"}'
         query = '{"op": "=", "left": "labels", "right": "' + label + '"}'
 
         resp = self.__session.get(
-            url=f"{self.__api_url}/domain-types/host/collections/all",
+            url=f'{self.__api_url}/domain-types/host/collections/all',
             params={
-                "query": query,
-                "columns": ['name', 'labels'],
+                'columns': ['name', 'labels'],
+                'query': query,
+                'sites': self.sites,
             },
         )
         if resp.status_code == 200:
@@ -608,11 +645,12 @@ class HostCacheRestApi(HostCache):
 
     def pre_fetch_hosts(self):
         LOGGER.debug(f'{self.backend} pre_fetch_hosts')
-
+        LOGGER.critical(f'{self.backend} pre_fetch_hosts sites {self.sites}')
         resp = self.__session.get(
-            url=f"{self.__api_url}/domain-types/host/collections/all",
+            url=f'{self.__api_url}/domain-types/host/collections/all',
             params={
-                "columns": ['name'],
+                'columns': ['name'],
+                'sites': self.sites,
             },
         )
         if resp.status_code == 200:
diff --git a/source/bin/nvdct/lib/constants.py b/source/bin/nvdct/lib/constants.py
new file mode 100755
index 0000000..076f649
--- /dev/null
+++ b/source/bin/nvdct/lib/constants.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# License: GNU General Public License v2
+# Author: thl-cmk[at]outlook[dot]com
+# URL   : https://thl-cmk.hopto.org
+# Date  : 2024-12-11
+# File  : nvdct/lib/constants.py
+
+
+from logging import  getLogger
+from os import environ
+from typing import Final
+
+NVDCT_VERSION: Final[str] = '0.9.4-20241210'
+#
+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_L3v4: 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_LLDP: Final[str] = "'nvdct/has_lldp_neighbours' 'yes'"
+LABEL_CDP: Final[str] = 'CDP'
+LABEL_L3v4: Final[str] = 'LAYER3v4'
+LABEL_LLDP: Final[str] = 'LLDP'
+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_L3v4: 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'
diff --git a/source/bin/nvdct/lib/settings.py b/source/bin/nvdct/lib/settings.py
index f8f4bc8..d40af53 100755
--- a/source/bin/nvdct/lib/settings.py
+++ b/source/bin/nvdct/lib/settings.py
@@ -2,7 +2,7 @@
 # -*- coding: utf-8 -*-
 #
 # License: GNU General Public License v2
-
+from cmk_addons.plugins.bgp_topology.lib.utils import OMD_ROOT
 # Author: thl-cmk[at]outlook[dot]com
 # URL   : https://thl-cmk.hopto.org
 # Date  : 2023-10-12
@@ -13,20 +13,29 @@
 from collections.abc import Mapping
 from ipaddress import AddressValueError, IPv4Address, IPv4Network, NetmaskValueError
 from logging import CRITICAL, FATAL, ERROR, WARNING, INFO, DEBUG
-from os import environ
 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,
+    LOGGER,
+    LOG_FILE,
+    OMD_ROOT,
+    TIME_FORMAT,
+    USER_DATA_FILE,
+)
 from lib.utils import (
     ExitCodes,
+    Layer,
     get_data_from_toml,
     get_local_cmk_api_port,
-    # get_local_cmk_version,
-    LOGGER,
-    TIME_FORMAT,
-    USER_DATA_FILE,
-    Layer
+    is_valid_customer_name,
+    is_valid_hostname,
+    is_valid_log_file,
+    is_valid_output_directory,
+    is_valid_site_name,
 )
 
 
@@ -45,11 +54,10 @@ class Thickness(NamedTuple):
 
 
 class StaticConnection(NamedTuple):
-    host: str
-    local_port: str
-    neighbour_port: str
-    neighbour: str
-    label: str
+    right_host: str
+    right_service: str
+    left_service: str
+    left_host: str
 
 
 class Wildcard(NamedTuple):
@@ -65,11 +73,6 @@ class Settings:
             self,
             cli_args: Mapping[str, object],
     ):
-        self.__omd_root = environ['OMD_ROOT']
-        self.__path_to_if_table = 'networking,interfaces'
-        self.__topology_file_name = 'network_data.json'
-        # self.__topology_save_path = 'var/topology_data'
-        self.__topology_save_path_cmk_2_3 = 'var/check_mk/topology/data'
         # cli defaults
         self.__settings = {
             # 'api_port': 80,
@@ -80,10 +83,11 @@ class Settings:
             'dont_compare': False,
             'filter_customers': None,
             'filter_sites': None,
+            'include_l3_hosts': False,
             'keep': False,
             'remove_domain': False,
-            'layers': ['CDP'],
-            'log_file': f'{self.omd_root}/var/log/nvdct.log',
+            'layers': [],
+            'log_file': LOG_FILE,
             'log_level': 'WARNING',
             'log_to_stdout': False,
             'min_age': 0,
@@ -91,11 +95,10 @@ class Settings:
             'prefix': None,
             'quiet': False,
             'pre_fetch': False,
-            # 'seed_devices': [],
             'skip_l3_if': False,
             'skip_l3_ip': False,
             'time_format': TIME_FORMAT,
-            'user_data_file': f'{self.omd_root}/local/bin/nvdct/conf/{USER_DATA_FILE}',
+            '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, __'
@@ -128,7 +131,6 @@ class Settings:
         self.__api_port: int | None = None
 
         # init user data with defaults
-        # self.__drop_host_regex: List[str] | None = None
         self.__custom_layers: List[StaticConnection] | None = None
         self.__customers: List[str] | None = None
         self.__emblems: Emblems | None = None
@@ -157,7 +159,7 @@ class Settings:
             else:
                 self.__api_port = get_local_cmk_api_port()
             if self.__api_port is None:
-                self.__api_port = 80
+                self.__api_port = API_PORT
 
         return self.__api_port
 
@@ -217,6 +219,10 @@ class Settings:
             )
         return None
 
+    @property  # --include-l3-hosts
+    def include_l3_hosts(self) -> bool:
+        return bool(self.__settings['include_l3_hosts'])
+
     @property  # --keep
     def keep(self) -> int | None:
         if isinstance(self.__settings['keep'], int):
@@ -233,27 +239,25 @@ class Settings:
 
     @property  # --log-file
     def log_file(self) -> str:
-        return str(self.__settings['log_file'])
+        raw_log_file = str(Path(str(self.__settings['log_file'])).expanduser())
+        if is_valid_log_file(raw_log_file):
+            return raw_log_file
+        else:
+            LOGGER.error(f'Falling back to {LOG_FILE}')
+            return LOG_FILE
 
     @property  # --log-level
     def loglevel(self) -> int:
-        match self.__settings['log_level']:
-            case 'DEBUG':
-                return DEBUG
-            case 'INFO':
-                return INFO
-            case 'WARNING':
-                return WARNING
-            case 'ERROR':
-                return ERROR
-            case 'FATAL':
-                return FATAL
-            case 'CRITICAL':
-                return CRITICAL
-            case 'OFF':
-                return -1
-            case _:
-                return WARNING
+        log_levels = {
+            'DEBUG': DEBUG,
+            'INFO': INFO,
+            'WARNING': WARNING,
+            'ERROR': ERROR,
+            'FATAL': FATAL,
+            'CRITICAL': CRITICAL,
+            'OFF': -1,
+        }
+        return log_levels.get(self.__settings['log_level'], WARNING)
 
     @property  # --log-to-stdout
     def log_to_stdtout(self) -> bool:
@@ -271,7 +275,11 @@ class Settings:
         # 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"])}'
-        return str(self.__settings['output_directory'])
+        if is_valid_output_directory(str(self.__settings['output_directory'])):
+            return str(self.__settings['output_directory'])
+        else:
+            LOGGER.error('Falling back to "nvdct"')
+            return 'nvdct'
 
     @property  # --prefix
     def prefix(self) -> str | None:
@@ -311,7 +319,7 @@ class Settings:
         if self.__customers is None:
             self.__customers = [
                 str(customer) for customer in set(self.__user_data.get('CUSTOMERS', []))
-            ]
+                if is_valid_customer_name(customer)]
             LOGGER.info(f'Found {len(self.__customers)} to filter on')
         return self.__customers
 
@@ -362,9 +370,7 @@ class Settings:
     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('L2_SEED_DEVICES', []) + self.__settings.get('seed_devices', [])
-                self.__user_data.get('L2_SEED_DEVICES', [])
-            )))
+                self.__user_data.get('L2_SEED_DEVICES', [])) if is_valid_hostname(host)))
         return self.__l2_seed_devices
 
     @property
@@ -373,7 +379,7 @@ class Settings:
             self.__l2_host_map = {
                 str(host): str(replace_host) for host, replace_host in self.__user_data.get(
                     'L2_HOST_MAP', {}
-                ).items()
+                ).items() if is_valid_hostname(host)
             }
         return self.__l2_host_map
 
@@ -392,7 +398,7 @@ class Settings:
         if self.__l3v4_ignore_hosts is None:
             self.__l3v4_ignore_hosts = [str(host) for host in set(self.__user_data.get(
                 'L3V4_IGNORE_HOSTS', []
-            ))]
+            )) if is_valid_hostname(host)]
         return self.__l3v4_ignore_hosts
 
     @property
@@ -460,9 +466,12 @@ class Settings:
                     _ip_network = IPv4Network(ip_network)  # noqa: F841
                 except (AddressValueError, NetmaskValueError):
                     LOGGER.error(
-                        f'Invalid entry in L3V4_REPLACE found: {ip_network} -> ignored'
+                        f'Invalid entry in L3V4_REPLACE found: {ip_network} -> line ignored'
                     )
                     continue
+                if not is_valid_hostname(node):
+                    LOGGER.error(f'Invalid node name found: {node} -> line ignored ')
+                    continue
                 self.__l3v4_replace[ip_network] = str(node)
             LOGGER.info(
                 f'Valid entries in L3V4_REPLACE found: {len(self.__l3v4_replace)}/'
@@ -526,18 +535,22 @@ class Settings:
             self.__static_connections = []
             for connection in self.__user_data.get('STATIC_CONNECTIONS', []):
                 try:
-                    host, local_port, neighbour_port, neighbour, label = connection
+                    left_host, left_service, right_service, right_host = connection
                 except ValueError:
                     LOGGER.error(
-                        f'Worng entry in STATIC_CONNECTIONS -> {connection} -> ignored'
+                        f'Wrong entry in STATIC_CONNECTIONS -> {connection} -> ignored'
                     )
                     continue
+                if not right_host or not left_host:
+                    LOGGER.warning(f'Both hosts must be set, got {connection}')
+                    continue
+                if not is_valid_hostname(right_host) or not is_valid_hostname(left_host):
+                    continue
                 self.__static_connections.append(StaticConnection(
-                    host=str(host),
-                    local_port=str(local_port),
-                    neighbour_port=str(neighbour_port),
-                    neighbour=str(neighbour),
-                    label=str(label),
+                    right_host=str(right_host),
+                    right_service=str(right_service),
+                    left_service=str(left_service),
+                    left_host=str(left_host),
                 ))
             LOGGER.info(
                 f'Valid entries in STATIC_CONNECTIONS found: {len(self.__static_connections)}/'
@@ -548,21 +561,6 @@ class Settings:
     @property
     def sites(self) -> List[str]:
         if self.__sites is None:
-            self.__sites = [str(site) for site in set(self.__user_data.get('SITES', []))]
-            LOGGER.info(f'fFound {len(self.__sites)} to filter on')
+            self.__sites = [str(site) for site in set(self.__user_data.get('SITES', [])) if is_valid_site_name(site)]
+            LOGGER.info(f'Found {len(self.__sites)} to filter on')
         return self.__sites
-
-    #
-    # all other settings
-    #
-    @property
-    def omd_root(self) -> str:
-        return self.__omd_root
-
-    @property
-    def topology_save_path(self) -> str:
-        return self.__topology_save_path_cmk_2_3
-
-    @property
-    def topology_file_name(self) -> str:
-        return self.__topology_file_name
diff --git a/source/bin/nvdct/lib/topologies.py b/source/bin/nvdct/lib/topologies.py
index 8c35a45..026b6fc 100755
--- a/source/bin/nvdct/lib/topologies.py
+++ b/source/bin/nvdct/lib/topologies.py
@@ -10,11 +10,32 @@
 
 from collections.abc import Mapping, Sequence
 from ipaddress import IPv4Address, IPv4Network
-from typing import Dict, List
-
-from lib.backends import CACHE_INTERFACES_DATA, CacheItems, HostCache, PATH_INTERFACES
-from lib.settings import Thickness, Wildcard
-from lib.utils import LOGGER
+from typing import Dict, List, Tuple
+from re import sub as re_sub
+
+from lib.backends import (
+    CacheItems,
+    HostCache,
+)
+from lib.constants import (
+    CACHE_INTERFACES_DATA,
+    HOST_LABEL_L3V4_HOSTS,
+    HOST_LABEL_L3V4_ROUTER,
+    LOGGER,
+    PATH_INTERFACES,
+    PATH_L3v4,
+)
+from lib.settings import (
+    Emblems,
+    StaticConnection,
+    Thickness,
+    Wildcard,
+)
+from lib.utils import (
+    InventoryColumns,
+    Ipv4Info,
+    is_valid_hostname,
+)
 
 
 class NvObjects:
@@ -23,12 +44,15 @@ class NvObjects:
         self.host_count: int = 0
         self.host_list: List[str] = []
 
-    def add_host_object(
+    def add_host(
         self,
         host: str,
         host_cache: HostCache,
         emblem: str | None = None
     ) -> None:
+        if not is_valid_hostname(host):
+            LOGGER.error(f'host not added! Invalid name {host}')
+            return
         if host not in self.nv_objects:
             self.host_count += 1
             self.host_list.append(host)
@@ -57,7 +81,45 @@ class NvObjects:
             }
             LOGGER.debug(f'host: {host}, link: {link}, metadata: {metadata}')
 
-    def add_service_object(
+    def add_service(
+        self,
+        host: str,
+        service: str,
+        host_cache: HostCache,
+        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)
+        service_object = f'{service}@{host}'
+        if service_object not in self.nv_objects:
+            link: Dict = {}
+            if host_cache.host_exists(host=host):
+                link = {'core': [host, service]}
+            elif emblem is not None:
+                metadata.update({
+                    'images': {
+                        'emblem': emblem,  # node image
+                        # 'icon': 'icon_tick',  # status image, top left from emblem
+                    },
+                    **({'tooltip': {'quickinfo': [{'name': 'Service node', 'value': service}]}} if not link else {})
+                })
+
+            self.nv_objects[service_object] = {
+                'name': name,
+                'link': link,
+                'metadata': metadata,
+            }
+        elif metadata is not {}:
+            self.nv_objects[service_object]['metadata'].update(metadata)
+
+
+    def add_interface(
         self,
         host: str,
         service: str,
@@ -73,7 +135,7 @@ class NvObjects:
             name = service
         speed = None
 
-        self.add_host_object(host=host, host_cache=host_cache)
+        self.add_host(host=host, host_cache=host_cache)
         service_object = f'{service}@{host}'
         if service_object not in self.nv_objects:
             link: Dict = {}
@@ -106,7 +168,7 @@ class NvObjects:
         elif metadata is not {}:
             self.nv_objects[service_object]['metadata'].update(metadata)
 
-    def add_ipv4_address_object(
+    def add_ipv4_address(
         self,
         host: str,
         ipv4_address: str,
@@ -135,7 +197,7 @@ class NvObjects:
                 }
             }
 
-    def add_ipv4_network_object(self, network: str, emblem: str, ) -> None:
+    def add_ipv4_network(self, network: str, emblem: str, ) -> None:
         if network not in self.nv_objects:
             self.nv_objects[network] = {
                 'name': network,
@@ -339,19 +401,19 @@ def get_service_by_interface(host: str, interface: str, host_cache: HostCache) -
         # 'management': 'Ma',
     }
 
-    def _get_short_if_name(interface: str) -> str:
+    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
+        :type interface_: str
         """
-        if not interface:
-            return interface
+        if not interface_:
+            return interface_
         for interface_prefix in _alternate_if_name.keys():
-            if interface.lower().startswith(interface_prefix.lower()):
+            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
+                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:
@@ -552,3 +614,387 @@ def get_list_of_devices(data: Mapping) -> List[str]:
     for connection in data.values():
         devices.append(connection[0])
     return list(set(devices))
+
+
+def create_static_connections(
+        connections_: Sequence[StaticConnection],
+        emblems: Emblems,
+        host_cache: HostCache,
+        nv_connections: NvConnections,
+        nv_objects: NvObjects,
+):
+    for connection in connections_:
+        LOGGER.info(msg=f'connection: {connection}')
+        nv_objects.add_host(
+            host=connection.right_host,
+            host_cache=host_cache,
+            emblem=emblems.host_node
+        )
+        nv_objects.add_host(
+            host=connection.left_host,
+            host_cache=host_cache,
+            emblem=emblems.host_node
+        )
+        if connection.right_service:
+            nv_objects.add_service(
+                host=connection.right_host,
+                host_cache=host_cache,
+                emblem=emblems.service_node,
+                service=connection.right_service
+            )
+            nv_connections.add_connection(
+                left=connection.right_host,
+                right=f'{connection.right_service}@{connection.right_host}',
+            )
+
+        if connection.left_service:
+            nv_objects.add_service(
+                host=connection.left_host,
+                host_cache=host_cache,
+                emblem=emblems.service_node,
+                service=connection.left_service
+            )
+            nv_connections.add_connection(
+                left=connection.left_host,
+                right=f'{connection.left_service}@{connection.left_host}',
+            )
+
+        if connection.right_service and connection.left_service:
+            nv_connections.add_connection(
+                left=f'{connection.right_service}@{connection.right_host}',
+                right=f'{connection.left_service}@{connection.left_host}',
+            )
+        elif connection.right_service:  # connect right_service with left_host
+            nv_connections.add_connection(
+                left=f'{connection.right_service}@{connection.right_host}',
+                right=f'{connection.left_host}',
+            )
+        elif connection.left_service:  # connect left_service with right_host
+            nv_connections.add_connection(
+                left=f'{connection.right_host}',
+                right=f'{connection.left_service}@{connection.left_host}',
+            )
+        else:  # connect right_host with left_host
+            nv_connections.add_connection(
+                left=f'{connection.right_host}',
+                right=f'{connection.left_host}',
+            )
+
+
+def create_l2_device_from_inv(
+        case: str,
+        host: str,
+        host_cache: HostCache,
+        inv_columns: InventoryColumns,
+        inv_data: Sequence[Mapping[str, str]],
+        l2_drop_hosts: List,
+        l2_host_map: Dict[str, str],
+        l2_neighbour_replace_regex: List[Tuple[str, str]] | None,
+        nv_connections: NvConnections,
+        nv_objects: NvObjects,
+        prefix: str,
+        remove_domain: bool,
+) -> None:
+    for topo_neighbour in inv_data:
+        # check if required data are not empty
+        if not (neighbour := topo_neighbour.get(inv_columns.neighbour)):
+            LOGGER.warning(f'incomplete data, neighbour missing {topo_neighbour}')
+            continue
+        if not (raw_local_port := topo_neighbour.get(inv_columns.local_port)):
+            LOGGER.warning(f'incomplete data, local port missing {topo_neighbour}')
+            continue
+        if not (raw_neighbour_port := topo_neighbour.get(inv_columns.neighbour_port)):
+            LOGGER.warning(f'incomplete data, neighbour port missing {topo_neighbour}')
+            continue
+
+        # drop neighbour before domain split
+        if neighbour in l2_drop_hosts:
+            LOGGER.info(msg=f'drop neighbour: {neighbour}')
+            continue
+
+        if l2_neighbour_replace_regex:
+            for re_str, replace_str in 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:
+                continue
+
+        if remove_domain:
+            neighbour = neighbour.split('.')[0]
+
+        # drop neighbour after domain split
+        if neighbour in l2_drop_hosts:
+            LOGGER.info(msg=f'drop neighbour: {neighbour}')
+            continue
+
+        if case == 'UPPER':
+            neighbour = neighbour.upper()
+            LOGGER.debug(f'Changed neighbour to upper case: {neighbour}')
+        elif case == 'LOWER':
+            neighbour = neighbour.lower()
+            LOGGER.debug(f'Changed neighbour to lower case: {neighbour}')
+
+        if prefix:
+            neighbour = f'{prefix}{neighbour}'
+        # rewrite neighbour if inventory neighbour and checkmk host don't match
+        if neighbour in l2_host_map.keys():
+            neighbour = l2_host_map[neighbour]
+
+        # getting/checking interfaces
+        local_port = get_service_by_interface(host, raw_local_port, host_cache)
+        if not local_port:
+            local_port = raw_local_port
+            LOGGER.warning(msg=f'service not found: host: {host}, raw_local_port: {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}'
+            )
+
+        neighbour_port = get_service_by_interface(neighbour, raw_neighbour_port, host_cache)
+        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}'
+            )
+        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}'
+            )
+
+        metadata = {
+            'duplex': topo_neighbour.get('duplex'),
+            'native_vlan': topo_neighbour.get('native_vlan'),
+        }
+
+        nv_objects.add_host(host=host, host_cache=host_cache)
+        nv_objects.add_host(host=neighbour, host_cache=host_cache)
+        nv_objects.add_interface(
+            host=host,
+            service=local_port,
+            host_cache=host_cache,
+            metadata=metadata,
+            name=raw_local_port,
+            item=local_port
+        )
+        nv_objects.add_interface(
+            host=neighbour,
+            service=neighbour_port,
+            host_cache=host_cache,
+            name=raw_neighbour_port,
+            item=neighbour_port
+        )
+        nv_connections.add_connection(
+            left=host,
+            right=f'{local_port}@{host}',
+        )
+        nv_connections.add_connection(
+            left=neighbour,
+            right=f'{neighbour_port}@{neighbour}',
+        )
+        nv_connections.add_connection(
+            left=f'{local_port}@{host}',
+            right=f'{neighbour_port}@{neighbour}',
+        )
+
+
+def create_l2_topology(
+        case: str,
+        host_cache: HostCache,
+        inv_columns: InventoryColumns,
+        l2_drop_hosts: List[str],
+        l2_host_map: Dict[str, str],
+        l2_neighbour_replace_regex: List[Tuple[str, str]],
+        label_: str,
+        nv_connections: NvConnections,
+        nv_objects: NvObjects,
+        path_in_inventory: str,
+        prefix: str,
+        remove_domain: bool,
+        seed_devices: Sequence[str],
+) -> None:
+    devices_to_go = list(set(seed_devices))  # remove duplicates
+    devices_done = []
+
+    while devices_to_go:
+        device = devices_to_go[0]
+
+        if device in l2_host_map.keys():
+            try:
+                devices_to_go.remove(device)
+            except ValueError:
+                pass
+            device = l2_host_map[device]
+            if device in devices_done:
+                continue
+
+        topo_data = host_cache.get_data(
+            host=device, item=CacheItems.inventory, path=path_in_inventory
+        )
+        if topo_data:
+            create_l2_device_from_inv(
+                host=device,
+                inv_data=topo_data,
+                inv_columns=inv_columns,
+                l2_host_map=l2_host_map,
+                l2_drop_hosts=l2_drop_hosts,
+                l2_neighbour_replace_regex=l2_neighbour_replace_regex,
+                host_cache=host_cache,
+                nv_objects=nv_objects,
+                nv_connections=nv_connections,
+                case=case,
+                prefix=prefix,
+                remove_domain=remove_domain
+            )
+
+            for _entry in 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: {label_}')
+
+
+def create_l3v4_topology(
+        emblems: Emblems,
+        host_cache: HostCache,
+        ignore_hosts: Sequence[str],
+        ignore_ips: Sequence[IPv4Network],
+        ignore_wildcard: Sequence[Wildcard],
+        include_hosts: bool,
+        nv_connections: NvConnections,
+        nv_objects: NvObjects,
+        replace: Mapping[str, str],
+        skip_if: bool,
+        skip_ip: bool,
+        summarize: Sequence[IPv4Network],
+) -> None:
+    host_list: Sequence[str] = host_cache.get_hosts_by_label(HOST_LABEL_L3V4_ROUTER)
+
+    if include_hosts:
+        host_list += host_cache.get_hosts_by_label(HOST_LABEL_L3V4_HOSTS)
+
+    LOGGER.debug(f'host list: {host_list}')
+    if not host_list:
+        LOGGER.warning(
+            msg='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'L3v4 ignore hosts: {ignore_hosts}')
+    for raw_host in host_list:
+        host = raw_host
+        if host in ignore_hosts:
+            LOGGER.info(f'L3v4 host {host} ignored')
+            continue
+        if not (ipv4_addresses := host_cache.get_data(
+            host=host, item=CacheItems.inventory, path=PATH_L3v4)
+        ):
+            LOGGER.warning(f'No IPv4 address inventory found for host: {host}')
+            continue
+
+        nv_objects.add_host(host=host, host_cache=host_cache)
+        for _entry in ipv4_addresses:
+            emblem = emblems.ip_network
+            try:
+                ipv4_info = Ipv4Info(**_entry)
+            except TypeError:  # as e
+                LOGGER.warning(f'Drop IPv4 address data for host: {host}, data: {_entry}')
+                continue
+
+            if ipv4_info.address.startswith('127.'):  # drop loopback addresses
+                LOGGER.info(f'host: {host} dropped loopback address: {ipv4_info.address}')
+                continue
+
+            if ipv4_info.cidr == 32:  # drop host addresses
+                LOGGER.info(
+                    f'host: {host} dropped host address: {ipv4_info.address}/{ipv4_info.cidr}'
+                )
+                continue
+
+            if ipv4_info.type.lower() != 'ipv4':  # drop if not ipv4
+                LOGGER.warning(
+                    f'host: {host} dropped non ipv4 address: {ipv4_info.address},'
+                    ' type: {ipv4_info.type}'
+                )
+                continue
+
+            if is_ignore_ipv4(ipv4_info.address, ignore_ips):
+                LOGGER.info(f'host: {host} dropped ignore address: {ipv4_info.address}')
+                continue
+
+            if is_ignore_wildcard(ipv4_info.address, ignore_wildcard):
+                LOGGER.info(f'host: {host} dropped wildcard address: {ipv4_info.address}')
+                continue
+
+            if network := get_network_summary(
+                ipv4_address=ipv4_info.address,
+                summarize=summarize,
+            ):
+                emblem = emblems.l3v4_summarize
+                LOGGER.info(
+                    f'Network summarized: {ipv4_info.network}/{ipv4_info.cidr} -> {network}'
+                )
+            else:
+                network = f'{ipv4_info.network}/{ipv4_info.cidr}'
+
+            if network in replace.keys():
+                LOGGER.info(f'Replaced network {network} with {replace[network]}')
+                network = replace[network]
+                emblem = emblems.l3v4_replace
+
+            nv_objects.add_ipv4_network(network=network, emblem=emblem)
+
+            if skip_if is True and skip_ip is True:
+                nv_connections.add_connection(left=host, right=network)
+            elif skip_if is True and skip_ip is False:
+                nv_objects.add_ipv4_address(
+                    host=host,
+                    interface=None,
+                    ipv4_address=ipv4_info.address,
+                    emblem=emblems.ip_address,
+                )
+                nv_objects.add_tooltip_quickinfo(
+                    '{ipv4_info.address}@{host}', 'Interface', ipv4_info.device
+                )
+                nv_connections.add_connection(left=f'{host}', right=f'{ipv4_info.address}@{host}')
+                nv_connections.add_connection(left=network, right=f'{ipv4_info.address}@{host}')
+            elif skip_if is False and skip_ip is True:
+                nv_objects.add_interface(
+                    host=host, service=ipv4_info.device, host_cache=host_cache
+                )
+                nv_objects.add_tooltip_quickinfo(
+                    f'{ipv4_info.device}@{host}', 'IP-address', ipv4_info.address
+                )
+                nv_connections.add_connection(left=f'{host}', right=f'{ipv4_info.device}@{host}')
+                nv_connections.add_connection(left=network, right=f'{ipv4_info.device}@{host}')
+            else:
+                nv_objects.add_ipv4_address(
+                    host=host,
+                    interface=ipv4_info.device,
+                    ipv4_address=ipv4_info.address,
+                    emblem=emblems.ip_address,
+                )
+                nv_objects.add_interface(
+                    host=host, service=ipv4_info.device, host_cache=host_cache,
+                )
+                nv_connections.add_connection(
+                    left=host, right=f'{ipv4_info.device}@{host}')
+                nv_connections.add_connection(
+                    left=f'{ipv4_info.device}@{host}',
+                    right=f'{ipv4_info.address}@{ipv4_info.device}@{host}',
+                )
+                nv_connections.add_connection(
+                    left=network, right=f'{ipv4_info.address}@{ipv4_info.device}@{host}',
+                )
diff --git a/source/bin/nvdct/lib/utils.py b/source/bin/nvdct/lib/utils.py
index 1857bce..4bd7f57 100755
--- a/source/bin/nvdct/lib/utils.py
+++ b/source/bin/nvdct/lib/utils.py
@@ -14,7 +14,6 @@ from enum import Enum, unique
 from json import dumps
 from logging import disable as log_off, Formatter, getLogger, StreamHandler
 from logging.handlers import RotatingFileHandler
-from os import environ
 from pathlib import Path
 from re import match as re_match
 from socket import socket, AF_UNIX, AF_INET, SOCK_STREAM, SHUT_WR
@@ -23,8 +22,23 @@ from time import time as now_time
 from tomllib import loads as toml_loads, TOMLDecodeError
 from typing import List, Dict, TextIO
 
-NVDCT_VERSION = '0.9.3-20241209'
-
+from lib.constants import (
+    CMK_SITE_CONF,
+    COLUMNS_CDP,
+    COLUMNS_LLDP,
+    HOST_LABEL_CDP,
+    HOST_LABEL_L3V4_ROUTER,
+    HOST_LABEL_LLDP,
+    LABEL_CDP,
+    LABEL_L3v4,
+    LABEL_LLDP,
+    LOGGER,
+    OMD_ROOT,
+    PATH_CDP,
+    PATH_L3v4,
+    PATH_LLDP,
+    DATAPATH,
+)
 
 @unique
 class ExitCodes(Enum):
@@ -33,15 +47,7 @@ class ExitCodes(Enum):
     BAD_TOML_FORMAT = 2
     BACKEND_NOT_IMPLEMENTED = 3
     AUTOMATION_SECRET_NOT_FOUND = 4
-
-
-@dataclass(frozen=True)
-class Layer:
-    path: str
-    columns: str
-    label: str
-    host_label: str
-
+    NO_LAYER_CONFIGURED = 5
 
 @dataclass(frozen=True)
 class Ipv4Info:
@@ -53,44 +59,18 @@ class Ipv4Info:
     network: str
     type: str
 
-
 @dataclass(frozen=True)
 class InventoryColumns:
     neighbour: str
     local_port: str
     neighbour_port: str
 
-
-# constants
-OMD_ROOT = environ["OMD_ROOT"]
-
-CACHE_INTERFACES_DATA = 'interface_data'
-CMK_SITE_CONF = f'{OMD_ROOT}/etc/omd/site.conf'
-COLUMNS_CDP = 'neighbour_name,local_port,neighbour_port'
-COLUMNS_L3v4 = 'address,device,cidr,network,type'
-COLUMNS_LLDP = 'neighbour_name,local_port,neighbour_port'
-HOME_URL = 'https://thl-cmk.hopto.org/gitlab/checkmk/vendor-independent/nvdct'
-HOST_LABEL_CDP = "'nvdct/has_cdp_neighbours' 'yes'"
-HOST_LABEL_L3V4 = "'nvdct/l3v4_topology' 'router'"
-HOST_LABEL_LLDP = "'nvdct/has_lldp_neighbours' 'yes'"
-LABEL_CDP = 'CDP'
-LABEL_L3v4 = 'LAYER3v4'
-LABEL_LLDP = 'LLDP'
-LOG_FILE = f'{OMD_ROOT}/var/log/nvdct.log'
-LOGGER = getLogger('root)')
-MIN_CDP_VERSION = '0.7.1-20240320'
-MIN_IP_ADDRESSES = '0.0.5-2024120'
-MIN_LLDP_VERSION = '0.9.3-20240320'
-PATH_CDP = 'networking,cdp_cache,neighbours'
-PATH_INTERFACES = 'networking,interfaces'
-PATH_L3v4 = 'networking,addresses'
-PATH_LLDP = 'networking,lldp_cache,neighbours'
-SAMPLE_SEEDS = 'Core01 Core02'
-SCRIPT = '~/local/bin/nvdct/nvdct.py'
-TIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%m'
-TIME_FORMAT_ARGPARSER = '%%Y-%%m-%%dT%%H:%%M:%%S.%%m'
-USER_DATA_FILE = 'nvdct.toml'
-
+@dataclass(frozen=True)
+class Layer:
+    path: str
+    columns: str
+    label: str
+    host_label: str
 
 LAYERS = {
     'CDP': Layer(
@@ -109,7 +89,7 @@ LAYERS = {
         path=PATH_L3v4,
         columns='',
         label=LABEL_L3v4,
-        host_label=HOST_LABEL_L3V4,
+        host_label=HOST_LABEL_L3V4_ROUTER,
     ),
 }
 
@@ -164,9 +144,7 @@ def get_data_from_toml(file: str) -> Dict:
 
 def rm_tree(root: Path) -> None:
     # safety
-    if not str(root).startswith(
-            f'{OMD_ROOT}/var/topology_data'
-    ) and not str(root).startswith(f'{OMD_ROOT}/var/check_mk/topology'):
+    if not str(root).startswith(DATAPATH):
         LOGGER.warning(msg=f"WARNING: bad path to remove, {str(root)}, don\'t delete it.")
         return
     for p in root.iterdir():
@@ -323,6 +301,47 @@ def is_list_of_str_equal(list1: List[str], list2: List[str]) -> bool:
     return tmp_list1 == tmp_list2
 
 
+def is_valid_hostname(host: str) -> bool:
+    re_host_pattern = r'^[0-9a-z-A-Z\.\-\_]{1,253}$'
+    if re_match(re_host_pattern, host):
+        return True
+    else:
+        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):
+        return True
+    else:
+        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):
+        return True
+    else:
+        LOGGER.error(f'Invalid customer name found: {customer}')
+        return False
+
+
+def is_valid_output_directory(directory: str) -> bool:
+    # 2024-12-11T17:35:08.12
+    re_host_pattern = r'^[0-9a-z-A-Z\.\-\_\:]{1,30}$'
+    if re_match(re_host_pattern, directory):
+        return True
+    else:
+        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()}')
+        return False
+    return True
+
+
 # not used in cmk 2.3.x format
 def merge_topologies(topo_pri: Dict, topo_sec: Dict) -> Dict:
     """
@@ -410,8 +429,7 @@ def configure_logger(log_file: str, log_level: int, log_to_console: bool) -> Non
 
     log = getLogger()
     log_formatter = Formatter(
-        fmt='%(asctime)s :: %(levelname)s :: %(module)s :'
-            ': %(funcName)s() :: %(lineno)s :: %(message)s',
+        fmt='%(asctime)s :: %(levelname)s :: %(module)s :: %(funcName)s() :: %(lineno)s :: %(message)s',
     )
 
     log.setLevel(log_level)
diff --git a/source/bin/nvdct/nvdct.py b/source/bin/nvdct/nvdct.py
index 16254c3..80755bf 100755
--- a/source/bin/nvdct/nvdct.py
+++ b/source/bin/nvdct/nvdct.py
@@ -133,7 +133,14 @@
 #             incompatible changed the option keep-domain to remove-domain -> don't mess with neighbor names by default
 # 2024-12-08: incompatible: changed hostlabel for L3v4 topology to nvdct/l3v4_topology
 #                           needs at least inv_ip_address inv_ip_address-0.0.5-20241209.mkp
-
+# 2024-12-09: added option --include-l3-hosts
+#             added site filter for RESTAPI backend
+#             enabled customer filter for MULTISITE backend
+# 2024-12-10: refactoring: moved topology code to topologies, removed all global variables, created main() function
+# 2024-12-11: incompatible: changed default layers to None -> use the CLI option -l CDP or the configfile instead
+#             incompatible: reworked static topology -> can now be used for each service, host/service name has to be
+#                           exactly like in CMK. See ~/local/bin/nvdct/conf/nfdct.toml
+#             moved string constants to lib/constants.py
 
 # creating topology data json from inventory data
 #
@@ -147,7 +154,9 @@
 # The inventory data could be created with my CDP/LLDP/IP Address/Interface nane inventory plugins:
 # CDP.....: https://thl-cmk.hopto.org/gitlab/checkmk/vendor-independent/inventory/inv_cdp_cache
 # LLDP....: https://thl-cmk.hopto.org/gitlab/checkmk/vendor-independent/inventory/inv_lldp_cache
-# L3v4....: https://thl-cmk.hopto.org/gitlab/checkmk/vendor-independent/inventory/inv_ipv4_addresses
+# L3v4....: https://thl-cmk.hopto.org/gitlab/checkmk/vendor-independent/inventory/inv_ip_addresses
+#         : https://thl-cmk.hopto.org/gitlab/checkmk/vendor-independent/inventory/inv_lnx_if_ip
+#         : https://thl-cmk.hopto.org/gitlab/checkmk/vendor-independent/inventory/inv_win_if_ip
 # IF Name.: https://thl-cmk.hopto.org/gitlab/checkmk/vendor-independent/inventory/inv_ifname
 #
 # USAGE:
@@ -229,554 +238,233 @@ __data = {
 """
 
 import sys
-from collections.abc import Mapping, Sequence
-from ipaddress import IPv4Network
 from logging import DEBUG
-from re import sub as re_sub
 from time import strftime, time_ns
-from typing import Dict, List, Tuple
+from typing import List
 
 from lib.args import parse_arguments
 from lib.backends import (
-    CacheItems,
     HostCache,
     HostCacheLiveStatus,
     HostCacheMultiSite,
     HostCacheRestApi,
 )
+from lib.constants import (
+    HOME_URL,
+    LOGGER,
+    NVDCT_VERSION,
+    DATAPATH,
+)
+from lib.settings import Settings
 from lib.topologies import (
     NvConnections,
     NvObjects,
-    # get_list_of_devices,
-    get_network_summary,
-    get_service_by_interface,
-    is_ignore_ipv4,
-    is_ignore_wildcard,
+    create_l2_topology,
+    create_l3v4_topology,
+    create_static_connections,
 )
 from lib.utils import (
-    configure_logger,
     ExitCodes,
-    HOME_URL,
-    HOST_LABEL_L3V4,
     InventoryColumns,
-    Ipv4Info,
-    Layer,
     LAYERS,
-    LOGGER,
-    # merge_topologies,
-    NVDCT_VERSION,
-    PATH_L3v4,
+    Layer,
+    StdoutQuiet,
+    configure_logger,
     remove_old_data,
     save_data_to_file,
-    # save_topology,
-    StdoutQuiet,
-)
-from lib.settings import (
-    Emblems,
-    Settings,
-    StaticConnection,
-    Thickness,
-    Wildcard,
 )
 
 
-EMBLEMS: Emblems
-HOST_CACHE: HostCache
-L2_DROP_HOSTS: List[str] = []
-L2_HOST_MAP: Dict[str, str] = {}
-L2_NEIGHBOUR_REPLACE_REGEX: List[Tuple[str, str]] | None = None
-MAP_SPEED_TO_THICKNESS: List[Thickness] = []
-NV_CONNECTIONS = NvConnections()
-NV_OBJECTS = NvObjects()
-SETTINGS: Settings
-
-
-def create_l2_device_from_inv(
-        host: str,
-        inv_data: Sequence[Mapping[str, str]],
-        inv_columns: InventoryColumns,
-        # label: str,
-) -> None:
-    for topo_neighbour in inv_data:
-        # check if required data are not empty
-        if not (neighbour := topo_neighbour.get(inv_columns.neighbour)):
-            LOGGER.warning(f'incomplete data, neighbour missing {topo_neighbour}')
-            continue
-        if not (raw_local_port := topo_neighbour.get(inv_columns.local_port)):
-            LOGGER.warning(f'incomplete data, local port missing {topo_neighbour}')
-            continue
-        if not (raw_neighbour_port := topo_neighbour.get(inv_columns.neighbour_port)):
-            LOGGER.warning(f'incomplete data, neighbour port missing {topo_neighbour}')
-            continue
-
-        # drop neighbour before domain split
-        if neighbour in L2_DROP_HOSTS:
-            LOGGER.info(msg=f'drop neighbour: {neighbour}')
-            continue
-
-        if L2_NEIGHBOUR_REPLACE_REGEX:
-            for re_str, replace_str in 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:
-                continue
-
-        if SETTINGS.remove_domain:
-            neighbour = neighbour.split('.')[0]
-
-        # drop neighbour after domain split
-        if neighbour in L2_DROP_HOSTS:
-            LOGGER.info(msg=f'drop neighbour: {neighbour}')
-            continue
-
-        if SETTINGS.case == 'UPPER':
-            neighbour = neighbour.upper()
-            LOGGER.debug(f'Changed neighbour to upper case: {neighbour}')
-        elif SETTINGS.case == 'LOWER':
-            neighbour = neighbour.lower()
-            LOGGER.debug(f'Changed neighbour to lower case: {neighbour}')
-
-        if SETTINGS.prefix:
-            neighbour = f'{SETTINGS.prefix}{neighbour}'
-        # rewrite neighbour if inventory neighbour and checkmk host don't match
-        if neighbour in L2_HOST_MAP.keys():
-            neighbour = L2_HOST_MAP[neighbour]
-
-        # getting/checking interfaces
-        local_port = get_service_by_interface(host, raw_local_port, HOST_CACHE)
-        if not local_port:
-            local_port = raw_local_port
-            LOGGER.warning(msg=f'service not found: host: {host}, raw_local_port: {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}'
-            )
-
-        neighbour_port = get_service_by_interface(neighbour, raw_neighbour_port, HOST_CACHE)
-        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}'
-            )
-        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}'
-            )
-
-        metadata = {
-            'duplex': topo_neighbour.get('duplex'),
-            'native_vlan': topo_neighbour.get('native_vlan'),
-        }
-
-        NV_OBJECTS.add_host_object(host=host, host_cache=HOST_CACHE)
-        NV_OBJECTS.add_host_object(host=neighbour, host_cache=HOST_CACHE)
-        NV_OBJECTS.add_service_object(
-            host=host,
-            service=local_port,
-            host_cache=HOST_CACHE,
-            metadata=metadata,
-            name=raw_local_port,
-            item=local_port
-        )
-        NV_OBJECTS.add_service_object(
-            host=neighbour,
-            service=neighbour_port,
-            host_cache=HOST_CACHE,
-            name=raw_neighbour_port,
-            item=neighbour_port
-        )
-        NV_CONNECTIONS.add_connection(
-            left=host,
-            right=f'{local_port}@{host}',
-        )
-        NV_CONNECTIONS.add_connection(
-            left=neighbour,
-            right=f'{neighbour_port}@{neighbour}',
-        )
-        NV_CONNECTIONS.add_connection(
-            left=f'{local_port}@{host}',
-            right=f'{neighbour_port}@{neighbour}',
-        )
-
-
-def create_static_connections(connections: Sequence[StaticConnection]):
-    for connection in connections:
-        LOGGER.info(msg=f'connection: {connection}')
-        NV_OBJECTS.add_host_object(
-            host=connection.host,
-            host_cache=HOST_CACHE,
-            emblem=EMBLEMS.host_node
-        )
-        NV_OBJECTS.add_host_object(
-            host=connection.neighbour,
-            host_cache=HOST_CACHE,
-            emblem=EMBLEMS.host_node
-        )
-        NV_OBJECTS.add_service_object(
-            host=connection.host,
-            host_cache=HOST_CACHE,
-            emblem=EMBLEMS.service_node,
-            service=connection.local_port
-        )
-        NV_OBJECTS.add_service_object(
-            host=connection.neighbour,
-            host_cache=HOST_CACHE,
-            emblem=EMBLEMS.service_node,
-            service=connection.neighbour_port
-        )
-        NV_CONNECTIONS.add_connection(
-            left=connection.host,
-            right=f'{connection.local_port}@{connection.host}',
-        )
-        NV_CONNECTIONS.add_connection(
-            left=connection.neighbour,
-            right=f'{connection.neighbour_port}@{connection.neighbour}',
-        )
-        NV_CONNECTIONS.add_connection(
-            left=f'{connection.local_port}@{connection.host}',
-            right=f'{connection.neighbour_port}@{connection.neighbour}',
-        )
-
-
-def create_l2_topology(
-        seed_devices: Sequence[str],
-        path_in_inventory: str,
-        inv_columns: InventoryColumns,
-        label: str,
-) -> None:
-    devices_to_go = list(set(seed_devices))  # remove duplicates
-    devices_done = []
-
-    while devices_to_go:
-        device = devices_to_go[0]
-
-        if device in L2_HOST_MAP.keys():
-            try:
-                devices_to_go.remove(device)
-            except ValueError:
-                pass
-            device = L2_HOST_MAP[device]
-            if device in devices_done:
-                continue
-
-        topo_data = HOST_CACHE.get_data(
-            host=device, item=CacheItems.inventory, path=path_in_inventory
-        )
-        if topo_data:
-            create_l2_device_from_inv(
-                host=device,
-                inv_data=topo_data,
-                inv_columns=inv_columns,
-                # label=label,
-            )
-
-            for _entry in 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: {label}')
-
-
-def create_l3v4_topology(
-        ignore_hosts: Sequence[str],
-        ignore_ips: Sequence[IPv4Network],
-        ignore_wildcard: Sequence[Wildcard],
-        summarize: Sequence[IPv4Network],
-        replace: Mapping[str, str],
-        skip_if: bool,
-        skip_ip: bool,
-) -> None:
-    host_list: Sequence[str] = HOST_CACHE.get_hosts_by_label(HOST_LABEL_L3V4)
-    LOGGER.debug(f'host list: {host_list}')
-    if not host_list:
-        LOGGER.warning(
-            msg='No routing capable host found. Check if "inv_ipv4_addresses.mkp" '
-                'added/enabled and inventory has run.'
-        )
-        return
-
-    LOGGER.debug(f'L3v4 ignore hosts: {ignore_hosts}')
-    for raw_host in host_list:
-        host = raw_host
-        if host in ignore_hosts:
-            LOGGER.info(f'L3v4 host {host} ignored')
-            continue
-        if not (ipv4_addresses := HOST_CACHE.get_data(
-            host=host, item=CacheItems.inventory, path=PATH_L3v4)
-        ):
-            LOGGER.warning(f'No IPv4 address inventory found for host: {host}')
-            continue
-
-        NV_OBJECTS.add_host_object(host=host, host_cache=HOST_CACHE)
-        for _entry in ipv4_addresses:
-            emblem = EMBLEMS.ip_network
-            try:
-                ipv4_info = Ipv4Info(**_entry)
-            except TypeError:  # as e
-                LOGGER.warning(f'Drop IPv4 address data for host: {host}, data: {_entry}')
-                continue
-
-            if ipv4_info.address.startswith('127.'):  # drop loopback addresses
-                LOGGER.info(f'host: {host} dropped loopback address: {ipv4_info.address}')
-                continue
-
-            if ipv4_info.cidr == 32:  # drop host addresses
-                LOGGER.info(
-                    f'host: {host} dropped host address: {ipv4_info.address}/{ipv4_info.cidr}'
-                )
-                continue
-
-            if ipv4_info.type.lower() != 'ipv4':  # drop if not ipv4
-                LOGGER.warning(
-                    f'host: {host} dropped non ipv4 address: {ipv4_info.address},'
-                    ' type: {ipv4_info.type}'
-                )
-                continue
-
-            if is_ignore_ipv4(ipv4_info.address, ignore_ips):
-                LOGGER.info(f'host: {host} dropped ignore address: {ipv4_info.address}')
-                continue
-
-            if is_ignore_wildcard(ipv4_info.address, ignore_wildcard):
-                LOGGER.info(f'host: {host} dropped wildcard address: {ipv4_info.address}')
-                continue
-
-            if network := get_network_summary(
-                ipv4_address=ipv4_info.address,
-                summarize=summarize,
-            ):
-                emblem = EMBLEMS.l3v4_summarize
-                LOGGER.info(
-                    f'Network summarized: {ipv4_info.network}/{ipv4_info.cidr} -> {network}'
-                )
-            else:
-                network = f'{ipv4_info.network}/{ipv4_info.cidr}'
-
-            if network in replace.keys():
-                LOGGER.info(f'Replaced network {network} with {replace[network]}')
-                network = replace[network]
-                emblem = EMBLEMS.l3v4_replace
-
-            NV_OBJECTS.add_ipv4_network_object(network=network, emblem=emblem)
-
-            if skip_if is True and skip_ip is True:
-                NV_CONNECTIONS.add_connection(left=host, right=network)
-            elif skip_if is True and skip_ip is False:
-                NV_OBJECTS.add_ipv4_address_object(
-                    host=host,
-                    interface=None,
-                    ipv4_address=ipv4_info.address,
-                    emblem=EMBLEMS.ip_address,
-                )
-                NV_OBJECTS.add_tooltip_quickinfo(
-                    '{ipv4_info.address}@{host}', 'Interface', ipv4_info.device
-                )
-                NV_CONNECTIONS.add_connection(left=f'{host}', right=f'{ipv4_info.address}@{host}')
-                NV_CONNECTIONS.add_connection(left=network, right=f'{ipv4_info.address}@{host}')
-            elif skip_if is False and skip_ip is True:
-                NV_OBJECTS.add_service_object(
-                    host=host, service=ipv4_info.device, host_cache=HOST_CACHE
-                )
-                NV_OBJECTS.add_tooltip_quickinfo(
-                    f'{ipv4_info.device}@{host}', 'IP-address', ipv4_info.address
-                )
-                NV_CONNECTIONS.add_connection(left=f'{host}', right=f'{ipv4_info.device}@{host}')
-                NV_CONNECTIONS.add_connection(left=network, right=f'{ipv4_info.device}@{host}')
-            else:
-                NV_OBJECTS.add_ipv4_address_object(
-                    host=host,
-                    interface=ipv4_info.device,
-                    ipv4_address=ipv4_info.address,
-                    emblem=EMBLEMS.ip_address,
-                )
-                NV_OBJECTS.add_service_object(
-                    host=host, service=ipv4_info.device, host_cache=HOST_CACHE,
-                )
-                NV_CONNECTIONS.add_connection(
-                    left=host, right=f'{ipv4_info.device}@{host}')
-                NV_CONNECTIONS.add_connection(
-                    left=f'{ipv4_info.device}@{host}',
-                    right=f'{ipv4_info.address}@{ipv4_info.device}@{host}',
-                )
-                NV_CONNECTIONS.add_connection(
-                    left=network, right=f'{ipv4_info.address}@{ipv4_info.device}@{host}',
-                )
-
-
-if __name__ == '__main__':
+def main():
     start_time = time_ns()
-    SETTINGS = Settings(vars(parse_arguments()))
 
-    sys.stdout = StdoutQuiet(quiet=SETTINGS.quiet)
+    nv_connections = NvConnections()
+    nv_objects = NvObjects()
+
+    settings: Settings = Settings(vars(parse_arguments()))
 
+    sys.stdout = StdoutQuiet(quiet=settings.quiet)
     configure_logger(
-        log_file=SETTINGS.log_file,
-        log_to_console=SETTINGS.log_to_stdtout,
-        log_level=SETTINGS.loglevel,
+        log_file=settings.log_file,
+        log_to_console=settings.log_to_stdtout,
+        log_level=settings.loglevel,
     )
-    LOGGER.info(msg='Data creation started')
+    # always logg start and end of a session (except --log-level OFF)
+    LOGGER.critical(msg='Data creation started')
 
-    print()
+    print('')
     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}'
     )
-    print()
-    print(f'Start time....: {strftime(SETTINGS.time_format)}')
+    print('')
+    print(f'Start time....: {strftime(settings.time_format)}')
 
-    match SETTINGS.backend:
+    match settings.backend:
         case 'RESTAPI':
-            HOST_CACHE = HostCacheRestApi(
-                pre_fetch=SETTINGS.pre_fetch,
-                api_port=SETTINGS.api_port
+            host_cache: HostCache = HostCacheRestApi(
+                pre_fetch=settings.pre_fetch,
+                api_port=settings.api_port,
+                filter_sites=settings.filter_sites,
+                sites=settings.sites
             )
         case 'MULTISITE':
-            HOST_CACHE = HostCacheMultiSite(
-                pre_fetch=SETTINGS.pre_fetch,
-                filter_sites=SETTINGS.filter_sites,
-                sites=SETTINGS.sites,
+            host_cache: HostCache = HostCacheMultiSite(
+                pre_fetch=settings.pre_fetch,
+                filter_sites=settings.filter_sites,
+                sites=settings.sites,
+                filter_customers=settings.filter_customers,
+                customers=settings.customers,
             )
         case 'LIVESTATUS':
-            HOST_CACHE = HostCacheLiveStatus(
-                pre_fetch=SETTINGS.pre_fetch,
+            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 (yet) implemented')
+            host_cache: HostCache | None = None  # to keep linter happy
             sys.exit(ExitCodes.BACKEND_NOT_IMPLEMENTED.value)
 
-    EMBLEMS = SETTINGS.emblems
-    L2_DROP_HOSTS = SETTINGS.l2_drop_hosts
-    L2_HOST_MAP = SETTINGS.l2_host_map
-    L2_NEIGHBOUR_REPLACE_REGEX = SETTINGS.l2_neighbour_replace_regex
-    MAP_SPEED_TO_THICKNESS = SETTINGS.map_speed_to_thickness
-
     jobs: List[Layer] = []
-    final_topology: Dict = {}
     pre_fetch_layers: List[str] = []
     pre_fetch_host_list: List[str] = []
 
-    for layer in SETTINGS.layers:
+    for layer in settings.layers:
         if layer == 'STATIC':
             jobs.append(layer)
         if layer == 'L3v4':
             jobs.append(layer)
-            HOST_CACHE.add_inventory_prefetch_path(path=LAYERS[layer].path)
+            host_cache.add_inventory_prefetch_path(path=LAYERS[layer].path)
             pre_fetch_layers.append(LAYERS[layer].host_label)
 
         elif layer in LAYERS:
             jobs.append(LAYERS[layer])
-            HOST_CACHE.add_inventory_prefetch_path(path=LAYERS[layer].path)
+            host_cache.add_inventory_prefetch_path(path=LAYERS[layer].path)
             pre_fetch_layers.append(LAYERS[layer].host_label)
 
         elif layer == 'CUSTOM':
-            for entry in SETTINGS.custom_layers:
+            for entry in settings.custom_layers:
                 jobs.append(entry)
-                HOST_CACHE.add_inventory_prefetch_path(entry.path)
+                host_cache.add_inventory_prefetch_path(entry.path)
+
+    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')
+        LOGGER.warning(message)
+        print(message)
+        sys.exit(ExitCodes.NO_LAYER_CONFIGURED.value)
 
-    if SETTINGS.pre_fetch:
+    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):
+            if _host_list := host_cache.get_hosts_by_label(host_label):
                 pre_fetch_host_list = list(set(pre_fetch_host_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.keys())}')
-        HOST_CACHE.pre_fetch_cache(pre_fetch_host_list)
+        print(f'Prefetch start: {strftime(settings.time_format)}')
+        print(f'Prefetch hosts: {len(pre_fetch_host_list)} of {len(host_cache.cache.keys())}')
+        host_cache.pre_fetch_cache(pre_fetch_host_list)
         LOGGER.info(f'Fetching data for {len(pre_fetch_host_list)} hosts end')
-        print(f'Prefetch end..: {strftime(SETTINGS.time_format)}')
+        print(f'Prefetch end..: {strftime(settings.time_format)}')
 
     for job in jobs:
         match job:
             case 'STATIC':
-                label = 'static'
+                label = 'STATIC'
                 create_static_connections(
-                    connections=SETTINGS.static_connections
+                    connections_=settings.static_connections,
+                    emblems=settings.emblems,
+                    host_cache=host_cache,
+                    nv_objects=nv_objects,
+                    nv_connections=nv_connections,
                 )
             case 'L3v4':
-                topology = None
-                label = 'l3v4'
+                label = 'L3v4'
                 create_l3v4_topology(
-                    ignore_hosts=SETTINGS.l3v4_ignore_hosts,
-                    ignore_ips=SETTINGS.l3v4_ignore_ips,
-                    ignore_wildcard=SETTINGS.l3v4_ignore_wildcard,
-                    summarize=SETTINGS.l3v4_summarize,
-                    replace=SETTINGS.l3v4_replace,
-                    skip_if=SETTINGS.skip_l3_if,
-                    skip_ip=SETTINGS.skip_l3_ip
+                    ignore_hosts=settings.l3v4_ignore_hosts,
+                    ignore_ips=settings.l3v4_ignore_ips,
+                    ignore_wildcard=settings.l3v4_ignore_wildcard,
+                    include_hosts=settings.include_l3_hosts,
+                    replace=settings.l3v4_replace,
+                    skip_if=settings.skip_l3_if,
+                    skip_ip=settings.skip_l3_ip,
+                    summarize=settings.l3v4_summarize,
+                    emblems=settings.emblems,
+                    host_cache=host_cache,
+                    nv_objects=nv_objects,
+                    nv_connections=nv_connections,
                 )
             case _:
-                label = job.label.lower()
+                label = job.label.upper()
                 columns = job.columns.split(',')
                 create_l2_topology(
-                    seed_devices=SETTINGS.l2_seed_devices,
+                    seed_devices=settings.l2_seed_devices,
                     path_in_inventory=job.path,
                     inv_columns=InventoryColumns(
                         neighbour=columns[0],
                         local_port=columns[1],
                         neighbour_port=columns[2]
                     ),
-                    label=label,
+                    label_=label,
+                    l2_drop_hosts=settings.l2_drop_hosts,
+                    l2_host_map=settings.l2_host_map,
+                    l2_neighbour_replace_regex=settings.l2_neighbour_replace_regex,
+                    host_cache=host_cache,
+                    nv_objects=nv_objects,
+                    nv_connections=nv_connections,
+                    case=settings.case,
+                    prefix=settings.prefix,
+                    remove_domain=settings.remove_domain,
                 )
 
-        NV_CONNECTIONS.add_meta_data_to_connections(
-            nv_objects=NV_OBJECTS,
-            speed_map=MAP_SPEED_TO_THICKNESS,
+        nv_connections.add_meta_data_to_connections(
+            nv_objects=nv_objects,
+            speed_map=settings.map_speed_to_thickness,
         )
-        if not SETTINGS.loglevel == DEBUG:
-            connections = NV_CONNECTIONS.nv_connections
-        else:
-            connections = sorted(NV_CONNECTIONS.nv_connections)
+
         _data = {
             'version': 1,
             'name': label,
-            'objects': NV_OBJECTS.nv_objects if not SETTINGS.loglevel == DEBUG else dict(
-                sorted(NV_OBJECTS.nv_objects.items())
+            'objects': nv_objects.nv_objects if not settings.loglevel == DEBUG else dict(
+                sorted(nv_objects.nv_objects.items())
             ),
-            'connections': connections
+            'connections': nv_connections.nv_connections if not settings.loglevel == DEBUG else sorted(
+                nv_connections.nv_connections
+            )
         }
         save_data_to_file(
             data=_data,
             path=(
-                f'{SETTINGS.omd_root}/{SETTINGS.topology_save_path}/'
-                f'{SETTINGS.output_directory}'
+                f'{DATAPATH}/{settings.output_directory}'
             ),
             file=f'data_{label}.json',
-            make_default=SETTINGS.default,
+            make_default=settings.default,
         )
 
         message = (
-            f'Source {label:.<7s}: Devices/Objects/Connections added {NV_OBJECTS.host_count}/'
-            f'{len(NV_OBJECTS.nv_objects)}/{len(NV_CONNECTIONS.nv_connections)}'
+            f'Layer {label:.<8s}: Devices/Objects/Connections added {nv_objects.host_count}/'
+            f'{len(nv_objects.nv_objects)}/{len(nv_connections.nv_connections)}'
         )
         LOGGER.info(msg=message)
         print(message)
 
-        NV_OBJECTS = NvObjects()
-        NV_CONNECTIONS = NvConnections()
+        nv_objects = NvObjects()
+        nv_connections = NvConnections()
 
-    if SETTINGS.keep:
+    if settings.keep:
         remove_old_data(
-            keep=SETTINGS.keep,
-            min_age=SETTINGS.min_age,
-            raw_path=f'{SETTINGS.omd_root}/{SETTINGS.topology_save_path}',
-            protected=SETTINGS.protected_topologies,
+            keep=settings.keep,
+            min_age=settings.min_age,
+            raw_path=DATAPATH,
+            protected=settings.protected_topologies,
         )
 
     print(f'Time taken....: {(time_ns() - start_time) / 1e9}/s')
-    print(f'End time......: {strftime(SETTINGS.time_format)}')
-    print()
+    print(f'End time......: {strftime(settings.time_format)}')
+    print('')
+
+    LOGGER.critical('Data creation finished')
 
-    LOGGER.info('Data creation finished')
+
+if __name__ == '__main__':
+    main()
diff --git a/source/packages/nvdct b/source/packages/nvdct
index 73ff4a1..d2a4e8f 100644
--- a/source/packages/nvdct
+++ b/source/packages/nvdct
@@ -17,8 +17,7 @@
                 'environments\n'
                 ' - Add custom connections (STATIC) for connections that are '
                 'not in the inventory\n'
-                ' - Optimized for my CDP, LLDP and IPv4 inventory plugins\n'
-                ' - Can also be used with custom inventory plugins\n'
+                ' - Optimized for my CDP, LLDP and IP inventory plugins\n'
                 '\n'
                 'For more information about the network visualization plugin '
                 'see: \n'
@@ -28,14 +27,7 @@
                 '\n'
                 'The inventory data could be created with my inventory '
                 'plugins:\n'
-                'CDP: '
-                'https://thl-cmk.hopto.org/gitlab/checkmk/vendor-independent/inventory/inv_cdp_cache\n'
-                'LLDP: '
-                'https://thl-cmk.hopto.org/gitlab/checkmk/vendor-independent/inventory/inv_lldp_cache\n'
-                'L3v4: '
-                'https://thl-cmk.hopto.org/gitlab/checkmk/vendor-independent/inventory/inv_ipv4_addresses\n'
-                'IF_name: '
-                'https://thl-cmk.hopto.org/gitlab/checkmk/vendor-independent/inventory/inv_ifname\n'
+                'https://thl-cmk.hopto.org/gitlab/explore/projects/topics/Network%20Visualization\n'
                 '\n'
                 'For the latest version and documentation see:\n'
                 'https://thl-cmk.hopto.org/gitlab/checkmk/vendor-independent/nvdct\n',
@@ -47,14 +39,15 @@
                    'nvdct/lib/utils.py',
                    'nvdct/lib/__init__.py',
                    'nvdct/conf/nvdct.toml',
-                   'nvdct/lib/topologies.py'],
+                   'nvdct/lib/topologies.py',
+                   'nvdct/lib/constants.py'],
            'web': ['htdocs/images/icons/cloud_80.png',
                    'htdocs/images/icons/ip-address_80.png',
                    'htdocs/images/icons/ip-network_80.png',
                    'htdocs/images/icons/location_80.png']},
  'name': 'nvdct',
  'title': 'Network Visualization Data Creation Tool (NVDCT)',
- 'version': '0.9.3-20241209',
+ 'version': '0.9.4-20241210',
  'version.min_required': '2.3.0b1',
  'version.packaged': 'cmk-mkp-tool 0.2.0',
  'version.usable_until': '2.4.0p1'}
-- 
GitLab