diff --git a/README.md b/README.md
index a34402a0092bc9e7d242dd79d38aae0915d0985b..4fa2cabe324d8470868b945a08cfee93511a6ac3 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,4 @@
+[PACKAGE]: ../../raw/master/mkp/cisco_asyncos-0.4.1-20240418.mkp "cisco_asyncos-0.4.1-20240418.mkp"
 # Cisco AsyncOS (IronPort)
 
 **Note: this package is for CheckMK version 2.x. For other versions see the corresponding branch.**
diff --git a/agent_based/cisco_asyncos_dns.py b/agent_based/cisco_asyncos_dns.py
deleted file mode 100644
index 534f79af3811fc2a7c0728a4d3546282d6e7d6dd..0000000000000000000000000000000000000000
--- a/agent_based/cisco_asyncos_dns.py
+++ /dev/null
@@ -1,90 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#
-# License: GNU General Public License v2
-#
-# Author: thl-cmk[at]outlook[dot]com
-# URL   : https://thl-cmk.hopto.org
-# Date  : 2020-03-08
-#
-# monitors status Cisco IronPort Appliances (ESA) DNS requests
-# Tested with: C380, C370
-#
-# 2021-03-24: rewrite for CMK 2.0
-#
-# .1.3.6.1.4.1.15497.1.1.1.15.0 = Gauge32: 0
-# .1.3.6.1.4.1.15497.1.1.1.16.0 = Gauge32: 0
-#
-# ASYNCOS-MAIL-MIB::outstandingDNSRequests.0 = Gauge32: 0
-# ASYNCOS-MAIL-MIB::pendingDNSRequests.0 = Gauge32: 0
-#
-#
-from typing import Mapping, List, NamedTuple, Optional
-
-from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
-    DiscoveryResult,
-    CheckResult,
-    StringTable,
-)
-
-from cmk.base.plugins.agent_based.agent_based_api.v1 import (
-    register,
-    Service,
-    check_levels,
-    Result,
-    Metric,
-    SNMPTree,
-    contains,
-    State,
-)
-
-
-class CiscoAsyncosDns(NamedTuple):
-    outstandingDNSRequests: int
-    pendingDNSRequests: int
-
-
-# [['0', '0']]
-def parse_cisco_asyncos_dns(string_table: StringTable) -> Optional[CiscoAsyncosDns]:
-    try:
-        return CiscoAsyncosDns(
-            outstandingDNSRequests=int(string_table[0][0]),
-            pendingDNSRequests=int(string_table[0][1]),
-        )
-    except IndexError:
-        return
-
-
-def discovery_cisco_asyncos_dns(section: CiscoAsyncosDns) -> DiscoveryResult:
-    yield Service()
-
-
-def check_cisco_asyncos_dns(params, section: CiscoAsyncosDns) -> CheckResult:
-    yield Result(state=State.OK,
-                 summary='%s outstanding, %s pending' % (
-                     section.outstandingDNSRequests, section.pendingDNSRequests))
-
-    yield Metric(name='outstandingDNSRequests', value=section.outstandingDNSRequests)
-    yield Metric(name='pendingDNSRequests', value=section.pendingDNSRequests)
-
-
-register.snmp_section(
-    name='cisco_asyncos_dns',
-    parse_function=parse_cisco_asyncos_dns,
-    fetch= SNMPTree(
-            base='.1.3.6.1.4.1.15497.1.1.1',  # ASYNCOS-MAIL-MIB
-            oids=[
-                '15',  # outstandingDNSRequests
-                '16',  # pendingDNSRequests
-            ]
-        ),
-    detect=contains('.1.3.6.1.2.1.1.1.0', 'AsyncOS'),
-)
-
-register.check_plugin(
-    name='cisco_asyncos_dns',
-    service_name='DNS requests',
-    discovery_function=discovery_cisco_asyncos_dns,
-    check_function=check_cisco_asyncos_dns,
-    check_default_parameters={},
-)
diff --git a/agent_based/cisco_asyncos_queue.py b/agent_based/cisco_asyncos_queue.py
deleted file mode 100644
index b4a7988b7fa40b412b82b19fc7d553b4b1340f8f..0000000000000000000000000000000000000000
--- a/agent_based/cisco_asyncos_queue.py
+++ /dev/null
@@ -1,105 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#
-# License: GNU General Public License v2
-#
-# Author: thl-cmk[at]outlook[dot]com
-# URL   : https://thl-cmk.hopto.org
-# Date  : 2020-02-10
-#
-#  added wato, fixed inventory function, changed snmp scan function, changed item to 'work queue'
-#
-# 2021-03-24: rewrite for CMK 2.0
-#
-
-from typing import Mapping, List, NamedTuple, Optional
-
-from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
-    DiscoveryResult,
-    CheckResult,
-    StringTable,
-)
-
-from cmk.base.plugins.agent_based.agent_based_api.v1 import (
-    register,
-    Service,
-    check_levels,
-    Result,
-    Metric,
-    SNMPTree,
-    contains,
-    State,
-)
-
-
-class CiscoAsyncosQueue(NamedTuple):
-    state: State
-    status_readable: str
-    workQueueMessages: int
-
-
-def get_status_readable(st: str) -> str:
-    states = {
-        '1': 'enough',
-        '2': 'near full',
-        '3': 'full',
-    }
-    return states.get(st, st)
-
-
-def get_cmk_state(st: str) -> State:
-    states = {
-        '1': State.OK,
-        '2': State.CRIT,
-        '3': State.WARN
-    }
-    return states.get(st, State.CRIT)
-
-
-# [['1', '1']]
-def parse_cisco_asyncos_messageage(string_table: StringTable) -> Optional[CiscoAsyncosQueue]:
-    try:
-        return CiscoAsyncosQueue(
-            state=get_cmk_state(string_table[0][0]),
-            status_readable=get_status_readable(string_table[0][0]),
-            workQueueMessages=int(string_table[0][1])
-        )
-    except IndexError:
-        return
-
-
-def discovery_cisco_asyncos_queue(section: CiscoAsyncosQueue) -> DiscoveryResult:
-    yield Service()
-
-
-def check_cisco_asyncos_queue(params, section: CiscoAsyncosQueue) -> CheckResult:
-    yield from check_levels(
-        value=section.workQueueMessages,
-        label='Messages in work queue',
-        levels_upper=params.get('upper', None),
-        metric_name='work_queue_size',
-    )
-    yield Result(state=section.state, notice='Queue space: %s' % section.status_readable)
-
-
-register.snmp_section(
-    name='cisco_asyncos_queue',
-    parse_function=parse_cisco_asyncos_messageage,
-    fetch= SNMPTree(
-            base='.1.3.6.1.4.1.15497.1.1.1',  # ASYNCOS-MAIL-MIB
-            oids=[
-                '5',  # queueAvailabilityStatus
-                '11'  # workQueueMessages
-            ]
-        ),
-    detect=contains('.1.3.6.1.2.1.1.1.0', 'AsyncOS'),
-)
-
-register.check_plugin(
-    name='cisco_asyncos_queue',
-    service_name='Queue',
-    discovery_function=discovery_cisco_asyncos_queue,
-    check_function=check_cisco_asyncos_queue,
-    check_default_parameters={'levels': (50, 100)},
-    check_ruleset_name='cisco_asyncos_queue',
-)
diff --git a/cisco_asyncos.mkp b/cisco_asyncos.mkp
deleted file mode 100644
index 60bcf605212b7552bb0db41df2d304ff042462d5..0000000000000000000000000000000000000000
Binary files a/cisco_asyncos.mkp and /dev/null differ
diff --git a/doc/.gitkeep b/img/.gitkeep
similarity index 100%
rename from doc/.gitkeep
rename to img/.gitkeep
diff --git a/doc/sample_fan.png b/img/sample_fan.png
similarity index 100%
rename from doc/sample_fan.png
rename to img/sample_fan.png
diff --git a/doc/sample_license.png b/img/sample_license.png
similarity index 100%
rename from doc/sample_license.png
rename to img/sample_license.png
diff --git a/doc/sample_power.png b/img/sample_power.png
similarity index 100%
rename from doc/sample_power.png
rename to img/sample_power.png
diff --git a/doc/sample_raid.png b/img/sample_raid.png
similarity index 100%
rename from doc/sample_raid.png
rename to img/sample_raid.png
diff --git a/doc/sample_temp.png b/img/sample_temp.png
similarity index 100%
rename from doc/sample_temp.png
rename to img/sample_temp.png
diff --git a/doc/sample_updates.png b/img/sample_updates.png
similarity index 100%
rename from doc/sample_updates.png
rename to img/sample_updates.png
diff --git a/doc/sample_workqueue.png b/img/sample_workqueue.png
similarity index 100%
rename from doc/sample_workqueue.png
rename to img/sample_workqueue.png
diff --git a/mkp/cisco_asyncos-0.4.1-20240418.mkp b/mkp/cisco_asyncos-0.4.1-20240418.mkp
new file mode 100644
index 0000000000000000000000000000000000000000..a794d4f34a5506ada4d5d0ed69d3d03c83785c39
Binary files /dev/null and b/mkp/cisco_asyncos-0.4.1-20240418.mkp differ
diff --git a/agent_based/cisco_asyncos_bandwidth.py b/source/agent_based/cisco_asyncos_bandwidth.py
similarity index 55%
rename from agent_based/cisco_asyncos_bandwidth.py
rename to source/agent_based/cisco_asyncos_bandwidth.py
index 4d739945661637de6bec45abec10d2f3e7dc64c8..eb6a701c37f96393d37cafec680267a2b24da096 100644
--- a/agent_based/cisco_asyncos_bandwidth.py
+++ b/source/agent_based/cisco_asyncos_bandwidth.py
@@ -7,27 +7,27 @@
 # URL   : https://thl-cmk.hopto.org
 # Date  : 2021-03-24
 #
-# only use full for Cisco WSA appliances
+# only useful for Cisco WSA appliances
 #
 # 2023-03-06: fixed: IndexError in parse function
-#
+# 2024-04-18: fixed bandwidth values to bytes/s (kb/s), see MIB
 
-from typing import Mapping, List, NamedTuple, Optional
+from typing import NamedTuple
 
 from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
-    DiscoveryResult,
     CheckResult,
+    DiscoveryResult,
+    HostLabelGenerator,
     StringTable,
 )
 
 from cmk.base.plugins.agent_based.agent_based_api.v1 import (
-    register,
+    HostLabel,
+    SNMPTree,
     Service,
     check_levels,
-    Result,
-    SNMPTree,
     contains,
-    State,
+    register,
     render,
 )
 
@@ -38,13 +38,22 @@ class CiscoAsyncosBandwidth(NamedTuple):
     bandwidth_day: int
 
 
+def host_label_cisco_asyncos(section: int) -> HostLabelGenerator:
+    """Host label function
+    Labels:
+        cmk/os_family:
+            This label is set to "AsyncOS" for all Cisco Ironport devices
+    """
+    yield HostLabel(name="cmk/os_family", value="AsyncOS")
+
+
 # [['0', '0', '247']]
-def parse_cisco_asyncos_bandwidth(string_table: StringTable) -> Optional[CiscoAsyncosBandwidth]:
+def parse_cisco_asyncos_bandwidth(string_table: StringTable) -> CiscoAsyncosBandwidth | None:
     try:
         return CiscoAsyncosBandwidth(
-            bandwidth_now=int(string_table[0][0]),
-            bandwidth_hour=int(string_table[0][1]),
-            bandwidth_day = int(string_table[0][2]),
+            bandwidth_now=int(string_table[0][0]) * 1000 / 8,  # kbits/s -> bytes/s
+            bandwidth_hour=int(string_table[0][1]) * 1000 / 8,
+            bandwidth_day=int(string_table[0][2]) * 1000 / 8,
         )
     except IndexError:
         return
@@ -56,44 +65,36 @@ def discovery_cisco_asyncos_bandwidth(section: CiscoAsyncosBandwidth) -> Discove
 
 def check_cisco_asyncos_bandwidth(params, section: CiscoAsyncosBandwidth) -> CheckResult:
     yield from check_levels(
-        section.bandwidth_now * 8,
+        value=section.bandwidth_now,
         label='Current bandwidth',
-        levels_upper=params.get('upper', None),
-        levels_lower=params.get('lower', None),
-        metric_name='bandwith_now',
+        # levels_upper=params.get('upper', None),  # not implemented
+        # levels_lower=params.get('lower', None),  # not implemented
+        metric_name='bandwidth_now',
         render_func=render.networkbandwidth,
-        # unit='bits/s'
     )
     yield from check_levels(
-        section.bandwidth_hour * 8,
-        label='last hour',
-        # levels_upper=params.get('upper', None),
-        # levels_lower=params.get('lower', None),
-        # metric_name='bandwith_now',
+        value=section.bandwidth_hour,
+        label='Last hour',
         render_func=render.networkbandwidth
     )
     yield from check_levels(
-        section.bandwidth_day * 8,
-        label='last day',
-        #levels_upper=params.get('upper', None),
-        #levels_lower=params.get('lower', None),
-        #metric_name='bandwith_now',
+        value=section.bandwidth_day,
+        label='Last day',
         render_func=render.networkbandwidth
     )
 
-    # yield Result(state=State.OK, summary='last hour: %s, last day: %s' % (section.bandwidth_hour, section.bandwidth_day))
-
 
 register.snmp_section(
     name='cisco_asyncos_bandwidth',
     parse_function=parse_cisco_asyncos_bandwidth,
-    fetch= SNMPTree(
-            base='.1.3.6.1.4.1.15497.1.2.3.7.4',  # ASYNCOSWEBSECURITYAPPLIANCE-MIB::proxyRecentBandWTotPerf
-            oids=[
-                '1',  # cacheBwidthTotalNow
-                '3',  # cacheBwidthTotal1hrMean
-                '5',  # cacheBwidthTotal1dayMean
-            ]
+    host_label_function=host_label_cisco_asyncos,
+    fetch=SNMPTree(
+        base='.1.3.6.1.4.1.15497.1.2.3.7.4',  # ASYNCOSWEBSECURITYAPPLIANCE-MIB::proxyRecentBandWTotPerf
+        oids=[
+            '1',  # cacheBwidthTotalNow (kb/s -> 1000 bits/s)
+            '3',  # cacheBwidthTotal1hrMean (kb/s)
+            '5',  # cacheBwidthTotal1dayMean (kb/s)
+        ]
     ),
     detect=contains('.1.3.6.1.2.1.1.1.0', 'AsyncOS'),
 )
diff --git a/agent_based/cisco_asyncos_cache.py b/source/agent_based/cisco_asyncos_cache.py
similarity index 53%
rename from agent_based/cisco_asyncos_cache.py
rename to source/agent_based/cisco_asyncos_cache.py
index bf535717efeeeb0d4f265621c5b4bb5f07de8fe1..54dac162d1be3e5de2f063fcd7cd62baebd972b7 100644
--- a/agent_based/cisco_asyncos_cache.py
+++ b/source/agent_based/cisco_asyncos_cache.py
@@ -7,26 +7,27 @@
 # URL   : https://thl-cmk.hopto.org
 # Date  : 2021-03-24
 #
-# only use full for Cisco WSA appliances
+# only useful for Cisco WSA appliances
 #
 
-from typing import Mapping, List, NamedTuple, Optional
+from typing import NamedTuple
 
 from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
-    DiscoveryResult,
     CheckResult,
+    DiscoveryResult,
+    HostLabelGenerator,
     StringTable,
 )
 
 from cmk.base.plugins.agent_based.agent_based_api.v1 import (
-    register,
-    Service,
-    check_levels,
-    Result,
+    HostLabel,
     Metric,
+    Result,
     SNMPTree,
-    contains,
+    Service,
     State,
+    contains,
+    register,
 )
 
 
@@ -38,15 +39,24 @@ class CiscoAsyncosCache(NamedTuple):
     cache_total_resptime: int
 
 
+def host_label_cisco_asyncos(section: int) -> HostLabelGenerator:
+    """Host label function
+    Labels:
+        cmk/os_family:
+            This label is set to "AsyncOS" for all Cisco Ironport devices
+    """
+    yield HostLabel(name="cmk/os_family", value="AsyncOS")
+
+
 # [['0', '0', '0', '0', '0']]
-def parse_cisco_asyncos_cache(string_table: StringTable) -> Optional[CiscoAsyncosCache]:
+def parse_cisco_asyncos_cache(string_table: StringTable) -> CiscoAsyncosCache | None:
     try:
         return CiscoAsyncosCache(
-           cache_hits_now=int(string_table[0][0]),
-           cache_misses_now=int(string_table[0][1]),
-           cache_hit_resptime_now=int(string_table[0][2]),
-           cache_miss_resptime_now=int(string_table[0][3]),
-           cache_total_resptime=int(string_table[0][4]),
+            cache_hits_now=int(string_table[0][0]),
+            cache_misses_now=int(string_table[0][1]),
+            cache_hit_resptime_now=int(string_table[0][2]),
+            cache_miss_resptime_now=int(string_table[0][3]),
+            cache_total_resptime=int(string_table[0][4]),
         )
     except IndexError:
         return
@@ -57,10 +67,14 @@ def discovery_cisco_asyncos_cache(section: CiscoAsyncosCache) -> DiscoveryResult
 
 
 def check_cisco_asyncos_cache(params, section: CiscoAsyncosCache) -> CheckResult:
-    yield Result(state=State.OK,
-                 summary='Cache Stats current %s Hits (%sms response time), %s Misses (%sms response time) and %sms total response time' % (
-                 section.cache_hits_now, section.cache_hit_resptime_now, section.cache_misses_now,
-                 section.cache_miss_resptime_now, section.cache_total_resptime))
+    yield Result(
+        state=State.OK,
+        summary=f'Cache Stats current {section.cache_hits_now} Hits '
+                f'({section.cache_hit_resptime_now}ms response time), '
+                f'{section.cache_misses_now} Misses '
+                f'({section.cache_miss_resptime_now}ms response time) and '
+                f'{section.cache_total_resptime}ms total response time'
+    )
 
     yield Metric(name='cache_hits_now', value=section.cache_hits_now)
     yield Metric(name='cache_misses_now', value=section.cache_misses_now)
@@ -68,19 +82,21 @@ def check_cisco_asyncos_cache(params, section: CiscoAsyncosCache) -> CheckResult
     yield Metric(name='cache_miss_resptime_now', value=section.cache_miss_resptime_now)
     yield Metric(name='cache_total_resptime', value=section.cache_total_resptime)
 
+
 register.snmp_section(
     name='cisco_asyncos_cache',
     parse_function=parse_cisco_asyncos_cache,
-    fetch= SNMPTree(
-            base='.1.3.6.1.4.1.15497.1.2.3.7',  # ASYNCOSWEBSECURITYAPPLIANCE-MIB::proxyRecentPerf
-            oids=[
-                '5.1',  # cacheHitsNow
-                '6.1',  # cacheMissesNow
-                '7.1',  # cacheHitRespTimeNow
-                '8.1',  # cacheMissRespTimeNow
-                '9.1',  # cacheTotalRespTimeNow
-            ]
-        ),
+    host_label_function=host_label_cisco_asyncos,
+    fetch=SNMPTree(
+        base='.1.3.6.1.4.1.15497.1.2.3.7',  # ASYNCOSWEBSECURITYAPPLIANCE-MIB::proxyRecentPerf
+        oids=[
+            '5.1',  # cacheHitsNow
+            '6.1',  # cacheMissesNow
+            '7.1',  # cacheHitRespTimeNow
+            '8.1',  # cacheMissRespTimeNow
+            '9.1',  # cacheTotalRespTimeNow
+        ]
+    ),
     detect=contains('.1.3.6.1.2.1.1.1.0', 'AsyncOS'),
 )
 
diff --git a/agent_based/cisco_asyncos_conn.py b/source/agent_based/cisco_asyncos_conn.py
similarity index 50%
rename from agent_based/cisco_asyncos_conn.py
rename to source/agent_based/cisco_asyncos_conn.py
index f5436cd60e0fb13855a5fec62ebb3f44005c09e9..c7e598667f7ba7d45ba628feb2441a90fde9d6f3 100644
--- a/agent_based/cisco_asyncos_conn.py
+++ b/source/agent_based/cisco_asyncos_conn.py
@@ -9,42 +9,52 @@
 #
 # only use full for Cisco WSA appliances
 #
-from typing import Mapping, List, NamedTuple, Optional
+from typing import NamedTuple
 
 from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
-    DiscoveryResult,
     CheckResult,
+    DiscoveryResult,
+    HostLabelGenerator,
     StringTable,
 )
 
 from cmk.base.plugins.agent_based.agent_based_api.v1 import (
-    register,
-    Service,
-    check_levels,
-    Result,
+    HostLabel,
     Metric,
+    Result,
     SNMPTree,
-    contains,
+    Service,
     State,
+    contains,
+    register,
 )
 
 
 class CiscoAsyncosConn(NamedTuple):
-    cacheClientIdleConns: int
-    cacheClientTotalConns: int
-    cacheClientMaxConns: int
+    cache_client_idle_conns: int
+    cache_client_total_conns: int
+    cache_client_max_conns: int
+
+
+def host_label_cisco_asyncos(section: int) -> HostLabelGenerator:
+    """Host label function
+    Labels:
+        cmk/os_family:
+            This label is set to "AsyncOS" for all Cisco Ironport devices
+    """
+    yield HostLabel(name="cmk/os_family", value="AsyncOS")
 
 
 # [['0', '5', '66277']]
-def parse_cisco_asyncos_conn(string_table: StringTable) -> Optional[CiscoAsyncosConn]:
+def parse_cisco_asyncos_conn(string_table: StringTable) -> CiscoAsyncosConn | None:
     try:
         return CiscoAsyncosConn(
-            cacheClientIdleConns=int(string_table[0][0]),
-            cacheClientTotalConns=int(string_table[0][1]),
-            cacheClientMaxConns=int(string_table[0][2]),
+            cache_client_idle_conns=int(string_table[0][0]),
+            cache_client_total_conns=int(string_table[0][1]),
+            cache_client_max_conns=int(string_table[0][2]),
         )
     except IndexError:
-        return  
+        return
 
 
 def discovery_cisco_asyncos_conn(section: CiscoAsyncosConn) -> DiscoveryResult:
@@ -52,26 +62,30 @@ def discovery_cisco_asyncos_conn(section: CiscoAsyncosConn) -> DiscoveryResult:
 
 
 def check_cisco_asyncos_conn(params, section: CiscoAsyncosConn) -> CheckResult:
-    yield Result(state=State.OK,
-                 summary='%s Active Connections - %s Idle Connections - Maximum Connections was %s' % (
-                     section.cacheClientTotalConns, section.cacheClientIdleConns, section.cacheClientMaxConns))
+    yield Result(
+        state=State.OK,
+        summary=f'{section.cache_client_total_conns} Active Connections - '
+                f'{section.cache_client_idle_conns} Idle Connections - Maximum Connections was '
+                f'{section.cache_client_max_conns}'
+    )
 
-    yield Metric(name='cacheClientIdleConns', value=section.cacheClientIdleConns)
-    yield Metric(name='cacheClientTotalConns', value=section.cacheClientTotalConns)
-    yield Metric(name='cacheClientMaxConns', value=section.cacheClientMaxConns)
+    yield Metric(name='cacheClientIdleConns', value=section.cache_client_idle_conns)
+    yield Metric(name='cacheClientTotalConns', value=section.cache_client_total_conns)
+    yield Metric(name='cacheClientMaxConns', value=section.cache_client_max_conns)
 
 
 register.snmp_section(
     name='cisco_asyncos_conn',
     parse_function=parse_cisco_asyncos_conn,
-    fetch= SNMPTree(
-            base='.1.3.6.1.4.1.15497.1.2.3.2',  # ASYNCOSWEBSECURITYAPPLIANCE-MIB::proxyClientSidePerf
-            oids=[
-                '7',  # cacheClientIdleConns
-                '8',  # cacheClientTotalConns
-                '9',  # cacheClientMaxConns
-            ]
-        ),
+    host_label_function=host_label_cisco_asyncos,
+    fetch=SNMPTree(
+        base='.1.3.6.1.4.1.15497.1.2.3.2',  # ASYNCOSWEBSECURITYAPPLIANCE-MIB::proxyClientSidePerf
+        oids=[
+            '7',  # cacheClientIdleConns
+            '8',  # cacheClientTotalConns
+            '9',  # cacheClientMaxConns
+        ]
+    ),
     detect=contains('.1.3.6.1.2.1.1.1.0', 'AsyncOS'),
 )
 
diff --git a/agent_based/cisco_asyncos_cpu.py b/source/agent_based/cisco_asyncos_cpu.py
similarity index 73%
rename from agent_based/cisco_asyncos_cpu.py
rename to source/agent_based/cisco_asyncos_cpu.py
index 51235007ffd2f3228fef560caa3fc45d63db9b97..bb00f79e5ec5d9f9fa146c21e5aa2733c9f002e8 100644
--- a/agent_based/cisco_asyncos_cpu.py
+++ b/source/agent_based/cisco_asyncos_cpu.py
@@ -7,28 +7,41 @@
 # URL   : https://thl-cmk.hopto.org
 # Date  : 2021-03-21
 
-from typing import Mapping, Dict, List, Tuple, NamedTuple
+# 2024-04-18: added host label cmk/os_family:AsyncOS
+
+from typing import List
 
 from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
-    DiscoveryResult,
     CheckResult,
+    DiscoveryResult,
+    HostLabelGenerator,
     StringTable,
 )
 
 from cmk.base.plugins.agent_based.agent_based_api.v1 import (
-    register,
+    HostLabel,
+    SNMPTree,
     Service,
     check_levels,
-    SNMPTree,
     contains,
+    register,
 )
 
 
+def host_label_cisco_asyncos(section: int) -> HostLabelGenerator:
+    """Host label function
+    Labels:
+        cmk/os_family:
+            This label is set to "AsyncOS" for all Cisco Ironport devices
+    """
+    yield HostLabel(name="cmk/os_family", value="AsyncOS")
+
+
 #
 # sample string_table
 # [[['41']]]
 #
-def parse_cisco_asyncos_cpu(string_table: List[StringTable]) -> int:
+def parse_cisco_asyncos_cpu(string_table: List[StringTable]) -> int | None:
     try:
         return int(string_table[0][0][0])
     except KeyError:
@@ -55,11 +68,12 @@ def check_cisco_asyncos_cpu(params, section: int) -> CheckResult:
 register.snmp_section(
     name='cisco_asyncos_cpu',
     parse_function=parse_cisco_asyncos_cpu,
+    host_label_function=host_label_cisco_asyncos,
     fetch=[
         SNMPTree(
-            base='.1.3.6.1.4.1.15497.1.1.1',
+            base='.1.3.6.1.4.1.15497.1.1.1',  # ASYNCOS-MAIL-MIB::asyncOSMailObjects
             oids=[
-                '2'  # ASYNCOS-MAIL-MIB::perCentCPUUtilization
+                '2'  # perCentCPUUtilization
             ]
         ),
     ],
diff --git a/source/agent_based/cisco_asyncos_dns.py b/source/agent_based/cisco_asyncos_dns.py
new file mode 100644
index 0000000000000000000000000000000000000000..dc25ff95cc345b3e8773af2fbdde9e90accfae1e
--- /dev/null
+++ b/source/agent_based/cisco_asyncos_dns.py
@@ -0,0 +1,112 @@
+#!/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  : 2020-03-08
+#
+# monitors status Cisco IronPort Appliances (ESA) DNS requests
+# Tested with: C380, C370
+#
+# 2021-03-24: rewrite for CMK 2.0
+# 2024-04-18: changed to check_levels
+#             added upper levels
+#             added metrics definition
+
+#
+# .1.3.6.1.4.1.15497.1.1.1.15.0 = Gauge32: 0
+# .1.3.6.1.4.1.15497.1.1.1.16.0 = Gauge32: 0
+#
+# ASYNCOS-MAIL-MIB::outstandingDNSRequests.0 = Gauge32: 0
+# ASYNCOS-MAIL-MIB::pendingDNSRequests.0 = Gauge32: 0
+#
+#
+from typing import NamedTuple
+
+from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
+    CheckResult,
+    DiscoveryResult,
+    HostLabelGenerator,
+    StringTable,
+)
+
+from cmk.base.plugins.agent_based.agent_based_api.v1 import (
+    HostLabel,
+    SNMPTree,
+    Service,
+    check_levels,
+    contains,
+    register,
+)
+
+
+class CiscoAsyncosDns(NamedTuple):
+    outstanding_dns_requests: int
+    pending_dns_requests: int
+
+
+def host_label_cisco_asyncos(section: int) -> HostLabelGenerator:
+    """Host label function
+    Labels:
+        cmk/os_family:
+            This label is set to "AsyncOS" for all Cisco Ironport devices
+    """
+    yield HostLabel(name="cmk/os_family", value="AsyncOS")
+
+
+# [['0', '0']]
+def parse_cisco_asyncos_dns(string_table: StringTable) -> CiscoAsyncosDns | None:
+    try:
+        return CiscoAsyncosDns(
+            outstanding_dns_requests=int(string_table[0][0]),
+            pending_dns_requests=int(string_table[0][1]),
+        )
+    except IndexError:
+        return
+
+
+def discovery_cisco_asyncos_dns(section: CiscoAsyncosDns) -> DiscoveryResult:
+    yield Service()
+
+
+def check_cisco_asyncos_dns(params, section: CiscoAsyncosDns) -> CheckResult:
+    yield from check_levels(
+        value=section.outstanding_dns_requests,
+        label='Outstanding requests',
+        metric_name='outstandingDNSRequests',
+        render_func=lambda v: f'{v}',
+        levels_upper=params.get('levels_outstanding')
+    )
+    yield from check_levels(
+        value=section.pending_dns_requests,
+        label='Pending requests',
+        metric_name='pendingDNSRequests',
+        render_func=lambda v: f'{v}',
+        levels_upper=params.get('levels_pending')
+    )
+
+
+register.snmp_section(
+    name='cisco_asyncos_dns',
+    parse_function=parse_cisco_asyncos_dns,
+    host_label_function=host_label_cisco_asyncos,
+    fetch=SNMPTree(
+        base='.1.3.6.1.4.1.15497.1.1.1',  # ASYNCOS-MAIL-MIB
+        oids=[
+            '15',  # outstandingDNSRequests
+            '16',  # pendingDNSRequests
+        ]
+    ),
+    detect=contains('.1.3.6.1.2.1.1.1.0', 'AsyncOS'),
+)
+
+register.check_plugin(
+    name='cisco_asyncos_dns',
+    service_name='DNS requests',
+    discovery_function=discovery_cisco_asyncos_dns,
+    check_function=check_cisco_asyncos_dns,
+    check_default_parameters={},
+    check_ruleset_name='cisco_asyncos_dns'
+)
diff --git a/agent_based/cisco_asyncos_fan.py b/source/agent_based/cisco_asyncos_fan.py
similarity index 82%
rename from agent_based/cisco_asyncos_fan.py
rename to source/agent_based/cisco_asyncos_fan.py
index 13e66181c0158755cc76f32de4cc55a853564f85..5e6995011e6b4ce700c6a8525424d64695be022e 100644
--- a/agent_based/cisco_asyncos_fan.py
+++ b/source/agent_based/cisco_asyncos_fan.py
@@ -44,25 +44,36 @@
 from typing import Dict, List
 
 from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
-    DiscoveryResult,
     CheckResult,
+    DiscoveryResult,
+    HostLabelGenerator,
     StringTable,
 )
 
 from cmk.base.plugins.agent_based.agent_based_api.v1 import (
-    register,
+    HostLabel,
+    SNMPTree,
     Service,
     check_levels,
-    State,
-    SNMPTree,
     contains,
+    register,
 )
 
+
+def host_label_cisco_asyncos(section: int) -> HostLabelGenerator:
+    """Host label function
+    Labels:
+        cmk/os_family:
+            This label is set to "AsyncOS" for all Cisco Ironport devices
+    """
+    yield HostLabel(name="cmk/os_family", value="AsyncOS")
+
+
 #
 # [[['6400', 'FAN 1'], ['6300', 'FAN 2'], ['6400', 'FAN 3'], ['6400', 'FAN 4'], ['6400', 'FAN 5'], ['6800', 'FAN 6']]]
 #
 
-def parse_cisco_asyncos_fan(string_table: List[StringTable]) -> dict:
+def parse_cisco_asyncos_fan(string_table: List[StringTable]) -> Dict:
     sensors = {}
     for sensor in string_table[0]:
         try:
@@ -83,23 +94,23 @@ def discovery_cisco_asyncos_fan(section: Dict) -> DiscoveryResult:
 def check_cisco_asyncos_fan(item, params, section) -> CheckResult:
     try:
         value = section[item]
-
-        yield from check_levels(
-            value,
-            label='Speed',
-            levels_lower=params.get('lower', None),
-            levels_upper=params.get('upper', None),
-            metric_name='fan' if params.get('output_metrics') else None,
-            render_func=lambda v: '%s RPM' % str(v),
-        )
-
     except KeyError:
-        pass
+        return
+
+    yield from check_levels(
+        value=value,
+        label='Speed',
+        levels_lower=params.get('lower', None),
+        levels_upper=params.get('upper', None),
+        metric_name='fan' if params.get('output_metrics') else None,
+        render_func=lambda v: f'{str(v)} RPM',
+    )
 
 
 register.snmp_section(
     name='cisco_asyncos_fan',
     parse_function=parse_cisco_asyncos_fan,
+    host_label_function=host_label_cisco_asyncos,
     fetch=[
         SNMPTree(
             base='.1.3.6.1.4.1.15497.1.1.1.10.1',  # ASYNCOS-MAIL-MIB::fanEntry
diff --git a/agent_based/cisco_asyncos_feature_keys.py b/source/agent_based/cisco_asyncos_feature_keys.py
similarity index 84%
rename from agent_based/cisco_asyncos_feature_keys.py
rename to source/agent_based/cisco_asyncos_feature_keys.py
index 4d75a73b4a4b1c43c9d46b653f2dac022255f279..37b078b1ca48f2303e884d5d80e83102582c2461 100644
--- a/agent_based/cisco_asyncos_feature_keys.py
+++ b/source/agent_based/cisco_asyncos_feature_keys.py
@@ -111,36 +111,45 @@ import time
 from typing import Mapping, List, NamedTuple
 
 from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
-    DiscoveryResult,
     CheckResult,
+    DiscoveryResult,
+    HostLabelGenerator,
     StringTable,
 )
 
 from cmk.base.plugins.agent_based.agent_based_api.v1 import (
-    register,
-    Service,
-    check_levels,
+    HostLabel,
     Result,
     SNMPTree,
-    contains,
+    Service,
     State,
-    render,
+    contains,
+    register,
 )
 
 
-def get_isperpetual(isperpetual):
+def get_is_perpetual(is_perpetual):
     s_status = {
         '1': True,
         '2': False
     }
-    return s_status.get(isperpetual, False)
+    return s_status.get(is_perpetual, False)
 
 
 class CiscoAsyncosLicense(NamedTuple):
     perpetual: bool
-    secondsuntilexpire: int
-    daysuntilexpire: int
-    expiredate: str
+    seconds_until_expire: int
+    days_until_expire: int
+    expire_date: str
+
+
+def host_label_cisco_asyncos(section: int) -> HostLabelGenerator:
+    """Host label function
+    Labels:
+        cmk/os_family:
+            This label is set to "AsyncOS" for all Cisco Ironport devices
+    """
+    yield HostLabel(name="cmk/os_family", value="AsyncOS")
 
 
 # [[
@@ -158,12 +167,12 @@ class CiscoAsyncosLicense(NamedTuple):
 #  ]]
 def parse_cisco_asyncos_feature_keys(string_table: List[StringTable]) -> Mapping[str, CiscoAsyncosLicense]:
     licenses = {}
-    for license, isperpetual, secondsuntilexpire in string_table[0]:
+    for license, is_perpetual, seconds_until_expire in string_table[0]:
         licenses.update({license: CiscoAsyncosLicense(
-            perpetual=get_isperpetual(isperpetual),
-            secondsuntilexpire=int(secondsuntilexpire),
-            daysuntilexpire=int(secondsuntilexpire) // 3600 // 24,
-            expiredate=time.strftime("%d %b %Y", time.localtime(time.time() + int(secondsuntilexpire)))
+            perpetual=get_is_perpetual(is_perpetual),
+            seconds_until_expire=int(seconds_until_expire),
+            days_until_expire=int(seconds_until_expire) // 3600 // 24,
+            expire_date=time.strftime("%d %b %Y", time.localtime(time.time() + int(seconds_until_expire)))
         )})
     return licenses
 
@@ -193,30 +202,37 @@ def check_cisco_asyncos_feature_keys(params, section: Mapping[str, CiscoAsyncosL
     ignore_count = 0
     for license in section.keys():
         if section[license].perpetual:
-            yield Result(state=State.OK, notice='%s is perpetual (will not expire)' % license)
+            yield Result(state=State.OK, notice=f'{license} is perpetual (will not expire)')
         elif license in features_ignore:
-            yield Result(state=State.OK, notice='%s will expire at %s (%s days). Feature ignored!' % (
-            license, section[license].expiredate, section[license].daysuntilexpire))
+            yield Result(
+                state=State.OK,
+                notice=f'{license} will expire at {section[license].expire_date} '
+                       f'({section[license].days_until_expire} days). Feature ignored!'
+            )
             ignore_count += 1
-        elif section[license].secondsuntilexpire == 0:
-            yield Result(state=State.CRIT, notice='%s has expired or is not licensed' % license)
+        elif section[license].seconds_until_expire == 0:
+            yield Result(state=State.CRIT, notice=f'{license} has expired or is not licensed')
         else:
-            if section[license].daysuntilexpire < crit:
+            if section[license].days_until_expire < crit:
                 state = State.CRIT
-            elif section[license].daysuntilexpire < warn:
+            elif section[license].days_until_expire < warn:
                 state = State.WARN
             else:
                 state = State.OK
 
-            yield Result(state=state, notice='%s will expire at %s (%s days)' % (
-            license, section[license].expiredate, section[license].daysuntilexpire))
+            yield Result(
+                state=state,
+                notice=f'{license} will expire at {section[license].expire_date} '
+                       f'({section[license].days_until_expire} days)'
+            )
 
-    yield Result(state=State.OK, summary='%d/%d Features found/ignored' % (len(section.keys()), ignore_count))
+    yield Result(state=State.OK, summary=f'{len(section.keys())}/{ignore_count} Features found/ignored')
 
 
 register.snmp_section(
     name='cisco_asyncos_feature_keys',
     parse_function=parse_cisco_asyncos_feature_keys,
+    host_label_function=host_label_cisco_asyncos,
     fetch=[
         SNMPTree(
             base='.1.3.6.1.4.1.15497.1.1.1.12.1',  # ASYNCOS-MAIL-MIB::keyExpirationEntry
diff --git a/agent_based/cisco_asyncos_mem.py b/source/agent_based/cisco_asyncos_mem.py
similarity index 75%
rename from agent_based/cisco_asyncos_mem.py
rename to source/agent_based/cisco_asyncos_mem.py
index f50d747dd751ecec25e4f6eba6d6964a578da2ad..237e57fc29cf0f557a5e93367df4e1093f82af28 100644
--- a/agent_based/cisco_asyncos_mem.py
+++ b/source/agent_based/cisco_asyncos_mem.py
@@ -7,6 +7,8 @@
 # URL   : https://thl-cmk.hopto.org
 # Date  : 2021-03-21
 #
+# 2024-04-18: fixed params for levels_upper (ThX to alexander[dot]laengle[at]lgt[dot]com)
+#
 # .1.3.6.1.4.1.15497.1.1.1.1.0 = INTEGER: 0
 # .1.3.6.1.4.1.15497.1.1.1.7.0 = INTEGER: 1
 #
@@ -17,31 +19,38 @@
 # [[u'0', u'1']]
 #
 
-from typing import Mapping, Dict, List, Tuple, NamedTuple
+from typing import List
 
 from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
-    DiscoveryResult,
     CheckResult,
+    DiscoveryResult,
+    HostLabelGenerator,
     StringTable,
 )
 
 from cmk.base.plugins.agent_based.agent_based_api.v1 import (
-    register,
+    HostLabel,
+    SNMPTree,
     Service,
     check_levels,
-    SNMPTree,
     contains,
+    register,
 )
 
 
-# factory_settings['cisco_asyncos_mem_default_levels'] = {
-#         'levels':(80.0, 90.0),
-#     }
+def host_label_cisco_asyncos(section: int) -> HostLabelGenerator:
+    """Host label function
+    Labels:
+        cmk/os_family:
+            This label is set to "AsyncOS" for all Cisco Ironport devices
+    """
+    yield HostLabel(name="cmk/os_family", value="AsyncOS")
+
 
 #
 # [[['48']]
 #
-def parse_cisco_asyncos_mem(string_table: List[StringTable]) -> int:
+def parse_cisco_asyncos_mem(string_table: List[StringTable]) -> int | None:
     try:
         return int(string_table[0][0][0])
     except KeyError:
@@ -53,21 +62,19 @@ def discovery_cisco_asyncos_mem(section: int) -> DiscoveryResult:
 
 
 def check_cisco_asyncos_mem(params, section: int) -> CheckResult:
-    def render_percent(value) -> str:
-        return '%s%%' % value
-
     yield from check_levels(
         section,
         label='Memory utilization',
-        levels_upper=params.get('util', None),
+        levels_upper=params.get('levels', None),
         metric_name='util',
-        render_func=render_percent,
+        render_func=lambda v: f'{v}%',
     )
 
 
 register.snmp_section(
     name='cisco_asyncos_mem',
     parse_function=parse_cisco_asyncos_mem,
+    host_label_function=host_label_cisco_asyncos,
     fetch=[
         SNMPTree(
             base='.1.3.6.1.4.1.15497.1.1.1',
diff --git a/agent_based/cisco_asyncos_messageage.py b/source/agent_based/cisco_asyncos_messageage.py
similarity index 72%
rename from agent_based/cisco_asyncos_messageage.py
rename to source/agent_based/cisco_asyncos_messageage.py
index 91383d7bf948143636414608c6dd257ac5f28606..9b6b7a81f9739b5e8627417bda329b9ec7921ee2 100644
--- a/agent_based/cisco_asyncos_messageage.py
+++ b/source/agent_based/cisco_asyncos_messageage.py
@@ -17,35 +17,44 @@
 # ASYNCOS-MAIL-MIB::oldestMessageAge.0 = Gauge32: 259706
 #
 
-from typing import Mapping, List, NamedTuple, Optional
+from typing import NamedTuple
 
 from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
-    DiscoveryResult,
     CheckResult,
+    DiscoveryResult,
+    HostLabelGenerator,
     StringTable,
 )
 
 from cmk.base.plugins.agent_based.agent_based_api.v1 import (
-    register,
+    HostLabel,
+    SNMPTree,
     Service,
     check_levels,
-    Result,
-    Metric,
-    SNMPTree,
     contains,
-    State,
+    register,
     render,
 )
 
 
 class CiscoAsyncosMessageage(NamedTuple):
-    messageage: int
+    message: int
+
+
+def host_label_cisco_asyncos(section: int) -> HostLabelGenerator:
+    """Host label function
+    Labels:
+        cmk/os_family:
+            This label is set to "AsyncOS" for all Cisco Ironport devices
+    """
+    yield HostLabel(name="cmk/os_family", value="AsyncOS")
+
 
 # [['259297']]
-def parse_cisco_asyncos_messageage(string_table: StringTable) -> Optional[CiscoAsyncosMessageage]:
+def parse_cisco_asyncos_messageage(string_table: StringTable) -> CiscoAsyncosMessageage | None:
     try:
         return CiscoAsyncosMessageage(
-            messageage=int(string_table[0][0]),
+            message=int(string_table[0][0]),
         )
     except IndexError:
         return
@@ -57,7 +66,7 @@ def discovery_cisco_asyncos_messageage(section: CiscoAsyncosMessageage) -> Disco
 
 def check_cisco_asyncos_messageage(params, section: CiscoAsyncosMessageage) -> CheckResult:
     yield from check_levels(
-        value=section.messageage,
+        value=section.message,
         label='Oldest messages',
         metric_name='message_age',
         levels_upper=params.get('upper', None),
@@ -68,12 +77,13 @@ def check_cisco_asyncos_messageage(params, section: CiscoAsyncosMessageage) -> C
 register.snmp_section(
     name='cisco_asyncos_messageage',
     parse_function=parse_cisco_asyncos_messageage,
-    fetch= SNMPTree(
-            base='.1.3.6.1.4.1.15497.1.1.1',  # ASYNCOS-MAIL-MIB
-            oids=[
-                '14',  # oldestMessageAge
-            ]
-        ),
+    host_label_function=host_label_cisco_asyncos,
+    fetch=SNMPTree(
+        base='.1.3.6.1.4.1.15497.1.1.1',  # ASYNCOS-MAIL-MIB
+        oids=[
+            '14',  # oldestMessageAge
+        ]
+    ),
     detect=contains('.1.3.6.1.2.1.1.1.0', 'AsyncOS'),
 )
 
diff --git a/agent_based/cisco_asyncos_power.py b/source/agent_based/cisco_asyncos_power.py
similarity index 88%
rename from agent_based/cisco_asyncos_power.py
rename to source/agent_based/cisco_asyncos_power.py
index 2ea635ef64099d743702862c95cbc8b86ee8a8ad..ee043d195dd40b7073e84d653ef956b8e8b458f4 100644
--- a/agent_based/cisco_asyncos_power.py
+++ b/source/agent_based/cisco_asyncos_power.py
@@ -35,28 +35,39 @@
 from typing import Dict, List, NamedTuple
 
 from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
-    DiscoveryResult,
     CheckResult,
+    DiscoveryResult,
+    HostLabelGenerator,
     StringTable,
 )
 
 from cmk.base.plugins.agent_based.agent_based_api.v1 import (
-    register,
-    Service,
+    HostLabel,
     Result,
-    State,
     SNMPTree,
+    Service,
+    State,
     contains,
+    register,
 )
 
 
 class CiscoAsyncosPowersupply(NamedTuple):
     redundancy: State
-    state: State
     redundancy_readable: str
+    state: State
     state_readable: str
 
 
+def host_label_cisco_asyncos(section: int) -> HostLabelGenerator:
+    """Host label function
+    Labels:
+        cmk/os_family:
+            This label is set to "AsyncOS" for all Cisco Ironport devices
+    """
+    yield HostLabel(name="cmk/os_family", value="AsyncOS")
+
+
 #
 # [[['2', '1', 'PS 1'], ['2', '1', 'PS 2']]]
 #
@@ -89,10 +100,10 @@ def parse_cisco_asyncos_power(string_table: List[StringTable]) -> dict:
             redundancy, redundancy_readable = get_ps_redundancy(redundancy)
 
             sensors.update({item: CiscoAsyncosPowersupply(
-                state=state,
-                state_readable=state_readable,
                 redundancy=redundancy,
                 redundancy_readable=redundancy_readable,
+                state=state,
+                state_readable=state_readable,
             )})
         except (ValueError, IndexError):
             pass
@@ -115,8 +126,8 @@ def check_cisco_asyncos_power(item, section) -> CheckResult:
     try:
         sensor = section[item]
 
-        yield Result(state=sensor.state, summary='Status: %s' % sensor.state_readable)
-        yield Result(state=sensor.redundancy, summary='Redundancy: %s' % sensor.redundancy_readable)
+        yield Result(state=sensor.state, summary=f'Status: {sensor.state_readable}')
+        yield Result(state=sensor.redundancy, summary=f'Redundancy: {sensor.redundancy_readable}')
 
     except KeyError:
         pass
@@ -125,6 +136,7 @@ def check_cisco_asyncos_power(item, section) -> CheckResult:
 register.snmp_section(
     name='cisco_asyncos_power',
     parse_function=parse_cisco_asyncos_power,
+    host_label_function=host_label_cisco_asyncos,
     fetch=[
         SNMPTree(
             base='.1.3.6.1.4.1.15497.1.1.1.8.1',  # ASYNCOS-MAIL-MIB::powerSupplyEntry
diff --git a/source/agent_based/cisco_asyncos_queue.py b/source/agent_based/cisco_asyncos_queue.py
new file mode 100644
index 0000000000000000000000000000000000000000..9792927695e5231c4bf344336403eb6384a062b0
--- /dev/null
+++ b/source/agent_based/cisco_asyncos_queue.py
@@ -0,0 +1,121 @@
+#!/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  : 2020-02-10
+#
+#  added wato, fixed inventory function, changed snmp scan function, changed item to 'work queue'
+#
+# 2021-03-24: rewrite for CMK 2.0
+# 2024-04-18: fixed wrong queue states (ThX to alexander[dot]laengle[at]lgt[dot]com)
+#             fixed levels for queue full not working (ThX to alexander[dot]laengle[at]lgt[dot]com)
+#             changed state for not defined state from CRIT to UNKNOWN
+#             a bit of clean up
+
+from typing import NamedTuple
+
+from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
+    CheckResult,
+    DiscoveryResult,
+    HostLabelGenerator,
+    StringTable,
+)
+
+from cmk.base.plugins.agent_based.agent_based_api.v1 import (
+    HostLabel,
+    Result,
+    SNMPTree,
+    Service,
+    State,
+    check_levels,
+    contains,
+    register,
+)
+
+
+class CiscoAsyncosQueue(NamedTuple):
+    state: State
+    state_readable: str
+    work_queue_messages: int | None
+
+
+def get_state_readable(st: str) -> str:
+    states = {
+        '1': 'enough',
+        '2': 'near full',
+        '3': 'full',
+    }
+    return states.get(st, st)
+
+
+def get_cmk_state(st: str) -> State:
+    states = {
+        '1': State.OK,
+        '2': State.WARN,
+        '3': State.CRIT
+    }
+    return states.get(st, State.UNKNOWN)
+
+
+def host_label_cisco_asyncos(section: int) -> HostLabelGenerator:
+    """Host label function
+    Labels:
+        cmk/os_family:
+            This label is set to "AsyncOS" for all Cisco Ironport devices
+    """
+    yield HostLabel(name="cmk/os_family", value="AsyncOS")
+
+
+# [['1', '3451']]
+def parse_cisco_asyncos_queue(string_table: StringTable) -> CiscoAsyncosQueue | None:
+    try:
+        queue_availability_status, work_queue_messages = string_table[0]
+    except IndexError:
+        return
+
+    return CiscoAsyncosQueue(
+        state=get_cmk_state(queue_availability_status),
+        state_readable=get_state_readable(queue_availability_status),
+        work_queue_messages=int(work_queue_messages) if work_queue_messages.isdigit() else None
+    )
+
+
+def discovery_cisco_asyncos_queue(section: CiscoAsyncosQueue) -> DiscoveryResult:
+    yield Service()
+
+
+def check_cisco_asyncos_queue(params, section: CiscoAsyncosQueue) -> CheckResult:
+    yield from check_levels(
+        value=section.work_queue_messages,
+        label='Messages in work queue',
+        levels_upper=params.get('levels', None),
+        metric_name='work_queue_size',
+    )
+    yield Result(state=section.state, notice=f'Queue space: {section.state_readable}')
+
+
+register.snmp_section(
+    name='cisco_asyncos_queue',
+    parse_function=parse_cisco_asyncos_queue,
+    host_label_function=host_label_cisco_asyncos,
+    fetch=SNMPTree(
+        base='.1.3.6.1.4.1.15497.1.1.1',  # ASYNCOS-MAIL-MIB::asyncOSMailObjects
+        oids=[
+            '5',  # queueAvailabilityStatus
+            '11'  # workQueueMessages
+        ]
+    ),
+    detect=contains('.1.3.6.1.2.1.1.1.0', 'AsyncOS'),
+)
+
+register.check_plugin(
+    name='cisco_asyncos_queue',
+    service_name='Queue',
+    discovery_function=discovery_cisco_asyncos_queue,
+    check_function=check_cisco_asyncos_queue,
+    check_default_parameters={'levels': (50, 100)},
+    check_ruleset_name='cisco_asyncos_queue',
+)
diff --git a/agent_based/cisco_asyncos_raid.py b/source/agent_based/cisco_asyncos_raid.py
similarity index 81%
rename from agent_based/cisco_asyncos_raid.py
rename to source/agent_based/cisco_asyncos_raid.py
index be917e5472a162c67580781ac9dfae1ac2014886..70d48a0b22775d5610b0f5a982e39ebef0f46507 100644
--- a/agent_based/cisco_asyncos_raid.py
+++ b/source/agent_based/cisco_asyncos_raid.py
@@ -36,25 +36,27 @@
 from typing import Mapping, List, NamedTuple
 
 from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
-    DiscoveryResult,
     CheckResult,
+    DiscoveryResult,
+    HostLabelGenerator,
     StringTable,
 )
 
 from cmk.base.plugins.agent_based.agent_based_api.v1 import (
-    register,
-    Service,
+    HostLabel,
     Result,
     SNMPTree,
-    contains,
+    Service,
     State,
+    contains,
+    register,
 )
 
 
 class CiscoAsyncosRaid(NamedTuple):
     state: State
-    status_readable: str
-    lasterror: str
+    state_readable: str
+    last_error: str
 
 
 def get_cmk_state(st: str) -> State:
@@ -66,7 +68,7 @@ def get_cmk_state(st: str) -> State:
     return states.get(st, State.CRIT)
 
 
-def get_status_readable(st: str) -> str:
+def get_state_readable(st: str) -> str:
     states = {
         '1': 'healthy',
         '2': 'failure',
@@ -75,6 +77,15 @@ def get_status_readable(st: str) -> str:
     return states.get(st, st)
 
 
+def host_label_cisco_asyncos(section: int) -> HostLabelGenerator:
+    """Host label function
+    Labels:
+        cmk/os_family:
+            This label is set to "AsyncOS" for all Cisco Ironport devices
+    """
+    yield HostLabel(name="cmk/os_family", value="AsyncOS")
+
+
 # [[
 #   ['1', 'Drive 0', 'No Errors'],
 #   ['2', 'Drive 1', 'UNKNOWN'],
@@ -85,11 +96,11 @@ def get_status_readable(st: str) -> str:
 #  ]]
 def parse_cisco_asyncos_raid(string_table: List[StringTable]) -> Mapping[str, CiscoAsyncosRaid]:
     arrays = {}
-    for raidStatus, raidID, raidLastError in string_table[0]:
-        arrays.update({raidID: CiscoAsyncosRaid(
-            state=get_cmk_state(raidStatus),
-            status_readable=get_status_readable(raidStatus),
-            lasterror=raidLastError
+    for raid_status, raid_id, raid_last_error in string_table[0]:
+        arrays.update({raid_id: CiscoAsyncosRaid(
+            state=get_cmk_state(raid_status),
+            state_readable=get_state_readable(raid_status),
+            last_error=raid_last_error
         )})
     return arrays
 
@@ -109,9 +120,10 @@ def discovery_cisco_asyncos_raid(section: Mapping[str, CiscoAsyncosRaid]) -> Dis
 
 def check_cisco_asyncos_raid(item, section: Mapping[str, CiscoAsyncosRaid]) -> CheckResult:
     try:
-        yield Result(state=section[item].state,
-                     summary='RAID status: %s, last error: %s' % (section[item].state, section[item].lasterror),
-                     )
+        yield Result(
+            state=section[item].state,
+            summary=f'RAID status: {section[item].state}, last error: {section[item].last_error}',
+        )
     except KeyError:
         pass
 
@@ -119,6 +131,7 @@ def check_cisco_asyncos_raid(item, section: Mapping[str, CiscoAsyncosRaid]) -> C
 register.snmp_section(
     name='cisco_asyncos_raid',
     parse_function=parse_cisco_asyncos_raid,
+    host_label_function=host_label_cisco_asyncos,
     fetch=[
         SNMPTree(
             base='.1.3.6.1.4.1.15497.1.1.1.18.1',  # ASYNCOS-MAIL-MIB::raidEntry
diff --git a/agent_based/cisco_asyncos_resources.py b/source/agent_based/cisco_asyncos_resources.py
similarity index 70%
rename from agent_based/cisco_asyncos_resources.py
rename to source/agent_based/cisco_asyncos_resources.py
index c6041a2d3abe8d0428103623c079085ade426925..a771e9b3d0d14b6e1b161f774fd50398773d3888 100644
--- a/agent_based/cisco_asyncos_resources.py
+++ b/source/agent_based/cisco_asyncos_resources.py
@@ -17,27 +17,27 @@
 # ASYNCOS-MAIL-MIB::resourceConservationReason.0 = INTEGER: noResourceConservation(1)
 #
 #
-from typing import Mapping, List, NamedTuple, Optional
+from typing import NamedTuple
 
 from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
-    DiscoveryResult,
     CheckResult,
+    DiscoveryResult,
+    HostLabelGenerator,
     StringTable,
 )
 
 from cmk.base.plugins.agent_based.agent_based_api.v1 import (
-    register,
-    Service,
-    check_levels,
+    HostLabel,
     Result,
     SNMPTree,
-    contains,
+    Service,
     State,
-    render,
+    contains,
+    register,
 )
 
 
-def get_status_readable(st: str) -> str:
+def get_state_readable(st: str) -> str:
     states = {
         '1': 'none',
         '2': 'memory shortage',
@@ -59,14 +59,23 @@ def get_cmk_state(st: str) -> State:
 
 class CiscoAsyncosResources(NamedTuple):
     state: State
-    status_readable: str
+    state_readable: str
+
+
+def host_label_cisco_asyncos(section: int) -> HostLabelGenerator:
+    """Host label function
+    Labels:
+        cmk/os_family:
+            This label is set to "AsyncOS" for all Cisco Ironport devices
+    """
+    yield HostLabel(name="cmk/os_family", value="AsyncOS")
 
 
-def parse_cisco_asyncos_bandwidth(string_table: StringTable) -> Optional[CiscoAsyncosResources]:
+def parse_cisco_asyncos_bandwidth(string_table: StringTable) -> CiscoAsyncosResources | None:
     try:
         return CiscoAsyncosResources(
             state=get_cmk_state(string_table[0][0]),
-            status_readable=get_status_readable(string_table[0][0])
+            state_readable=get_state_readable(string_table[0][0])
         )
     except IndexError:
         return
@@ -77,18 +86,19 @@ def discovery_cisco_asyncos_resources(section: CiscoAsyncosResources) -> Discove
 
 
 def check_cisco_asyncos_resources(section: CiscoAsyncosResources) -> CheckResult:
-    yield Result(state=section.state, notice='Resource conservation %s' % section.status_readable)
+    yield Result(state=section.state, notice=f'Resource conservation {section.state_readable}')
 
 
 register.snmp_section(
     name='cisco_asyncos_resources',
     parse_function=parse_cisco_asyncos_bandwidth,
-    fetch= SNMPTree(
-            base='.1.3.6.1.4.1.15497.1.1.1',  # ASYNCOS-MAIL-MIB
-            oids=[
-                '6',  # resourceConservationReason
-            ]
-        ),
+    host_label_function=host_label_cisco_asyncos,
+    fetch=SNMPTree(
+        base='.1.3.6.1.4.1.15497.1.1.1',  # ASYNCOS-MAIL-MIB
+        oids=[
+            '6',  # resourceConservationReason
+        ]
+    ),
     detect=contains('.1.3.6.1.2.1.1.1.0', 'AsyncOS'),
 )
 
diff --git a/agent_based/cisco_asyncos_temp.py b/source/agent_based/cisco_asyncos_temp.py
similarity index 75%
rename from agent_based/cisco_asyncos_temp.py
rename to source/agent_based/cisco_asyncos_temp.py
index bb38d10d341d2a051c2e0d097f6ea39eccb6efa5..4782bb6644ae4b2dc6f51b6297565ec480785921 100644
--- a/agent_based/cisco_asyncos_temp.py
+++ b/source/agent_based/cisco_asyncos_temp.py
@@ -22,33 +22,43 @@
 # ASYNCOS-MAIL-MIB::temperatureName.1 = STRING: Ambient
 #
 
-from typing import Mapping, Dict, List, Tuple, NamedTuple
+from typing import Dict
 
 from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
-    DiscoveryResult,
     CheckResult,
+    DiscoveryResult,
+    HostLabelGenerator,
     StringTable,
 )
 
 from cmk.base.plugins.agent_based.agent_based_api.v1 import (
-    register,
-    Service,
-    check_levels,
+    HostLabel,
     SNMPTree,
+    Service,
     contains,
     get_value_store,
+    register,
 )
 
 from cmk.base.plugins.agent_based.utils.temperature import (
-    check_temperature,
     TempParamType,
+    check_temperature,
 )
 
 
+def host_label_cisco_asyncos(section: int) -> HostLabelGenerator:
+    """Host label function
+    Labels:
+        cmk/os_family:
+            This label is set to "AsyncOS" for all Cisco Ironport devices
+    """
+    yield HostLabel(name="cmk/os_family", value="AsyncOS")
+
+
 #
 # [['22', 'FP_TEMP_SENSOR']]
 #
-def parse_cisco_asyncos_temp(string_table: StringTable) -> dict:
+def parse_cisco_asyncos_temp(string_table: StringTable) -> Dict:
     sensors = {}
     for sensor in string_table:
         try:
@@ -70,7 +80,7 @@ def check_cisco_asyncos_temp(item, params: TempParamType, section) -> CheckResul
         yield from check_temperature(
             section[item],
             params=params,
-            unique_name='check_cisco_asyncos_temp.%s' % item,
+            unique_name=f'check_cisco_asyncos_temp.{item}',
             value_store=get_value_store(),
         )
     except KeyError:
@@ -80,13 +90,14 @@ def check_cisco_asyncos_temp(item, params: TempParamType, section) -> CheckResul
 register.snmp_section(
     name='cisco_asyncos_temp',
     parse_function=parse_cisco_asyncos_temp,
-    fetch= SNMPTree(
-            base='.1.3.6.1.4.1.15497.1.1.1.9.1',  # ASYNCOS-MAIL-MIB::temperatureEntry
-            oids=[
-                '2',  # degreesCelsius
-                '3',  # temperatureName
-            ]
-        ),
+    host_label_function=host_label_cisco_asyncos,
+    fetch=SNMPTree(
+        base='.1.3.6.1.4.1.15497.1.1.1.9.1',  # ASYNCOS-MAIL-MIB::temperatureEntry
+        oids=[
+            '2',  # degreesCelsius
+            '3',  # temperatureName
+        ]
+    ),
     detect=contains('.1.3.6.1.2.1.1.1.0', 'AsyncOS'),
 )
 
diff --git a/agent_based/cisco_asyncos_updates.py b/source/agent_based/cisco_asyncos_updates.py
similarity index 82%
rename from agent_based/cisco_asyncos_updates.py
rename to source/agent_based/cisco_asyncos_updates.py
index db567ceb11480cd6540e79e69f7d821881f58aad..f6c8bf51690a14b5bfa12a882b549a8a8b60ca6a 100644
--- a/agent_based/cisco_asyncos_updates.py
+++ b/source/agent_based/cisco_asyncos_updates.py
@@ -137,22 +137,24 @@
 # ASYNCOS-MAIL-MIB::updateFailures.14 = Counter32: 0
 # ASYNCOS-MAIL-MIB::updateFailures.15 = Counter32: 0
 #
-from typing import Mapping, List, NamedTuple
+from typing import List, Mapping, NamedTuple
 
 from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
-    DiscoveryResult,
     CheckResult,
+    DiscoveryResult,
+    HostLabelGenerator,
     StringTable,
 )
 
 from cmk.base.plugins.agent_based.agent_based_api.v1 import (
-    register,
-    Service,
+    HostLabel,
+    Metric,
     Result,
     SNMPTree,
-    contains,
+    Service,
     State,
-    Metric,
+    contains,
+    register,
 )
 
 from cmk.base.item_state import (
@@ -160,9 +162,20 @@ from cmk.base.item_state import (
     set_item_state,
 )
 
+
 class CiscoAsyncosUpdate(NamedTuple):
     updates: int
-    updatefailures: int
+    update_failures: int
+
+
+def host_label_cisco_asyncos(section: int) -> HostLabelGenerator:
+    """Host label function
+    Labels:
+        cmk/os_family:
+            This label is set to "AsyncOS" for all Cisco Ironport devices
+    """
+    yield HostLabel(name="cmk/os_family", value="AsyncOS")
+
 
 # [[
 #   ['File Reputation', '6', '38'],
@@ -186,10 +199,11 @@ def parse_cisco_asyncos_updates(string_table: List[StringTable]) -> Mapping[str,
     for feature, updates, update_failures in string_table[0]:
         features.update({feature: CiscoAsyncosUpdate(
             updates=int(updates),
-            updatefailures=int(update_failures)
-        ) })
+            update_failures=int(update_failures)
+        )})
     return features
 
+
 # {
 #  'File Reputation': CiscoAsyncosUpdate(updates=6, updatefailures=38),
 #  'IronPort Anti-Spam': CiscoAsyncosUpdate(updates=12, updatefailures=9),
@@ -207,25 +221,24 @@ def parse_cisco_asyncos_updates(string_table: List[StringTable]) -> Mapping[str,
 #  'support_request': CiscoAsyncosUpdate(updates=1, updatefailures=0),
 #  'timezones': CiscoAsyncosUpdate(updates=1, updatefailures=0)
 # }
-def discovery_cisco_asyncos_updates(section:Mapping[str, CiscoAsyncosUpdate])-> DiscoveryResult:
+def discovery_cisco_asyncos_updates(section: Mapping[str, CiscoAsyncosUpdate]) -> DiscoveryResult:
     yield Service()
 
-def check_cisco_asyncos_updates(params, section:Mapping[str, CiscoAsyncosUpdate]) -> CheckResult:
+
+def check_cisco_asyncos_updates(params, section: Mapping[str, CiscoAsyncosUpdate]) -> CheckResult:
     features_ignore = params.get('features_ignore', [])
     warn, crit = params.get('failedLevel')
     ignore_count = 0
     for feature in section.keys():
-        failed = section[feature].updatefailures
+        failed = section[feature].update_failures
         passed = section[feature].updates
 
         # read counters
-        passedLast = get_item_state('cisco_asyncos_updates_%s_passedLast' % feature)
-        failedLast = get_item_state('cisco_asyncos_updates_%s_failedLast' % feature)
-        failedAttempts = get_item_state('cisco_asyncos_updates_%s_failedAttempts' % feature)
+        passed_last = get_item_state('cisco_asyncos_updates_%s_passedLast' % feature)
+        failed_last = get_item_state('cisco_asyncos_updates_%s_failedLast' % feature)
+        failed_attempts = get_item_state('cisco_asyncos_updates_%s_failedAttempts' % feature)
 
-        # print('Feature %s, passed: %s/%s failed: %s/%s, Attempts: %s' % (feature, passed,passedLast, failed,failedLast, failedAttempts))
-
-        if (passedLast == None) or (failedLast == None) or (failedAttempts == None):  # or (lastState == None):
+        if (passed_last is None) or (failed_last is None) or (failed_attempts is None):  # or (lastState is None):
             # init counters
             set_item_state('cisco_asyncos_updates_%s_passedLast' % feature, passed)
             set_item_state('cisco_asyncos_updates_%s_failedLast' % feature, failed)
@@ -233,50 +246,52 @@ def check_cisco_asyncos_updates(params, section:Mapping[str, CiscoAsyncosUpdate]
         else:
             set_item_state('cisco_asyncos_updates_%s_passedLast' % feature, passed)
             set_item_state('cisco_asyncos_updates_%s_failedLast' % feature, failed)
-            passedLast = int(passedLast)
-            failedLast = int(failedLast)
-            failedAttempts = int(failedAttempts)
+            passed_last = int(passed_last)
+            failed_last = int(failed_last)
+            failed_attempts = int(failed_attempts)
 
             # reset counter if overrun
-            if failed < failedLast:
+            if failed < failed_last:
                 set_item_state('cisco_asyncos_updates_%s_failedLast' % feature, failed)
-                failedLast = failed
-            if passed < passedLast:
+                failed_last = failed
+            if passed < passed_last:
                 set_item_state('cisco_asyncos_updates_%s_passedLast' % feature, passed)
-                passedLast = passed
+                passed_last = passed
 
-            if passed > passedLast:
+            if passed > passed_last:
                 # reset error counter after passed update attempt
                 set_item_state('cisco_asyncos_updates_%s_failedAttempts' % feature, 0)
             else:
-                failedAttempts = failedAttempts + failed - failedLast
-                set_item_state('cisco_asyncos_updates_%s_failedAttempts' % feature, failedAttempts)
+                failed_attempts = failed_attempts + failed - failed_last
+                set_item_state('cisco_asyncos_updates_%s_failedAttempts' % feature, failed_attempts)
+                base_message = f'{feature}: {passed}/{failed}'
                 if feature in features_ignore:
-                    yield Result(state=State.OK, notice='%s: %d/%d updates/update failures. Feature ignored!' % (feature, passed, failed))
+                    yield Result(state=State.OK, notice=f'{base_message} updates/update failures. Feature ignored!')
                     ignore_count += 1
                 else:
-                    if failedAttempts >= crit:
-                        yield Result(state=State.CRIT, notice='%s: %d/%d passed/failed.' % (feature, passed, failed))
-                        lastState=-1
-                    elif failedAttempts >= warn:
-                        yield Result(state=State.WARN, notice='%s: %d/%d passed/failed.' % (feature, passed, failed))
-                        lastState=-1
+                    if failed_attempts >= crit:
+                        yield Result(state=State.CRIT, notice=f'{base_message} passed/failed.')
+                        last_state = -1
+                    elif failed_attempts >= warn:
+                        yield Result(state=State.WARN, notice=f'{base_message} passed/failed.')
+                        last_state = -1
                     else:
-                        yield Result(state=State.OK, notice='%s: %d/%d passed/failed.' % (feature, passed, failed))
-                        lastState=1
+                        yield Result(state=State.OK, notice=f'{base_message} passed/failed.')
+                        last_state = 1
 
                     yield Metric(
                         name=feature.replace(' ', '_'),
-                        value=lastState,
-                        boundaries=(2,-2)
+                        value=last_state,
+                        boundaries=(2, -2)
                     )
 
-    yield Result(state=State.OK, summary='%d/%d Features found/ignored' % (len(section.keys()), ignore_count))
+    yield Result(state=State.OK, summary=f'{len(section.keys())}/{ignore_count} Features found/ignored')
 
 
 register.snmp_section(
     name='cisco_asyncos_updates',
     parse_function=parse_cisco_asyncos_updates,
+    host_label_function=host_label_cisco_asyncos,
     fetch=[
         SNMPTree(
             base='.1.3.6.1.4.1.15497.1.1.1.13.1',  # ASYNCOS-MAIL-MIB::updateEntry
@@ -298,4 +313,3 @@ register.check_plugin(
     check_default_parameters={'failedLevel': (5, 10)},
     check_ruleset_name='cisco_asyncos_updates',
 )
-
diff --git a/source/gui/metrics/cisco_asyncos.py b/source/gui/metrics/cisco_asyncos.py
new file mode 100644
index 0000000000000000000000000000000000000000..68350c8c2f797eb7ff42490a5d5b03a707319739
--- /dev/null
+++ b/source/gui/metrics/cisco_asyncos.py
@@ -0,0 +1,100 @@
+#!/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-04-18
+# File  : cisco_asyncos.py (metrics)
+#
+
+from cmk.gui.i18n import _
+
+from cmk.gui.plugins.metrics.utils import (
+    metric_info,
+    graph_info,
+    perfometer_info
+)
+
+#
+# asyncos bandwidth
+#
+
+metric_info['bandwidth_now'] = {
+    'title': _('Current bandwidth'),
+    'unit': 'bytes/s',
+    'color': '31/a',
+}
+
+graph_info['cisco_asyncos.bandwidth_now'] = {
+    'title': _('Cisco AsyncOS current bandwidth'),
+    'metrics': [
+        ('bandwidth_now', 'area'),
+    ],
+}
+
+perfometer_info.append({
+    'type': 'logarithmic',
+    'metric': 'bandwidth_now',
+    'half_value': 1073741824,  # 1GByte
+    'exponent': 2,
+})
+
+#
+# asyncos dns
+#
+
+metric_info['outstandingDNSRequests'] = {
+    'title': _('Outstanding DNS requests'),
+    'unit': 'count',
+    'color': '31/a',
+}
+metric_info['pendingDNSRequests'] = {
+    'title': _('Pending DNS requests'),
+    'unit': 'count',
+    'color': '21/a',
+}
+graph_info['cisco_asyncos.dns'] = {
+    'title': _('Cisco AsyncOS DNS requests'),
+    'metrics': [
+        ('outstandingDNSRequests', 'area'),
+        ('pendingDNSRequests', '-area'),
+    ],
+}
+perfometer_info.append(('stacked', [
+    {
+        'type': 'logarithmic',
+        'metric': 'outstandingDNSRequests',
+        'half_value': 100,
+        'exponent': 2,
+    },
+    {
+        'type': 'logarithmic',
+        'metric': 'pendingDNSRequests',
+        'half_value': 100,
+        'exponent': 2,
+    }
+]))
+
+#
+# asyncos message queue
+#
+
+metric_info['work_queue_size'] = {
+    'title': _('Work queue size'),
+    'unit': 'count',
+    'color': '11/a',
+}
+
+graph_info['cisco_asyncos.queue'] = {
+    'title': _('Cisco AsyncOS work queue'),
+    'metrics': [
+        ('work_queue_size', 'area'),
+    ],
+}
+perfometer_info.append({
+    'type': 'logarithmic',
+    'metric': 'work_queue_size',
+    'half_value': 100,
+    'exponent': 2,
+})
diff --git a/source/gui/wato/cisco_asyncos_dns.py b/source/gui/wato/cisco_asyncos_dns.py
new file mode 100644
index 0000000000000000000000000000000000000000..02b399bc4cb72f2b3b1a4cd2baf2e1c07020e364
--- /dev/null
+++ b/source/gui/wato/cisco_asyncos_dns.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+#
+from cmk.gui.i18n import _
+from cmk.gui.valuespec import (
+    Dictionary,
+    Integer,
+    TextAscii,
+    Tuple,
+)
+
+from cmk.gui.plugins.wato.utils import (
+    CheckParameterRulespecWithItem,
+    RulespecGroupCheckParametersNetworking,
+    rulespec_registry,
+)
+
+
+def _parameter_valuespec_cisco_asyncos_dns():
+    return Dictionary(elements=[
+        ('levels_outstanding',
+         Tuple(
+             title=_('Upper levels for outstanding DNS requests'),
+             help=_('Number of DNS requests that have been sent but for which no reply has been received.'),
+             elements=[
+                 Integer(title=_('Warning at'), unit=_('# of requests')),
+                 Integer(title=_('Critical at'), unit=_('# of requests')),
+             ])
+         ),
+        ('levels_pending',
+         Tuple(
+             title=_('Upper levels for pending DNS requests'),
+             help=_('Number of DNS requests waiting to be sent.'),
+             elements=[
+                 Integer(title=_('Warning'), unit=_('# of requests')),
+                 Integer(title=_('Critical'), unit=_('# of requests')),
+             ])
+         ),
+    ])
+
+
+rulespec_registry.register(
+    CheckParameterRulespecWithItem(
+        check_group_name='cisco_asyncos_dns',
+        group=RulespecGroupCheckParametersNetworking,
+        item_spec=lambda: TextAscii(title=_('Cisco AsyncOS DNS'), ),
+        match_type='dict',
+        parameter_valuespec=_parameter_valuespec_cisco_asyncos_dns,
+        title=lambda: _('Cisco AsyncOS DNS'),
+    ))
diff --git a/gui/wato/cisco_asyncos_feature_keys.py b/source/gui/wato/cisco_asyncos_feature_keys.py
similarity index 87%
rename from gui/wato/cisco_asyncos_feature_keys.py
rename to source/gui/wato/cisco_asyncos_feature_keys.py
index 8b38a99ae5511dee47bc8a2adc4ed95a8b5d9df1..a42936026b662cddc7c8015b255bb587ccacb0ec 100644
--- a/gui/wato/cisco_asyncos_feature_keys.py
+++ b/source/gui/wato/cisco_asyncos_feature_keys.py
@@ -6,15 +6,15 @@ from cmk.gui.i18n import _
 from cmk.gui.valuespec import (
     Dictionary,
     Integer,
-    TextAscii,
     ListOfStrings,
+    TextAscii,
     Tuple,
 )
 
 from cmk.gui.plugins.wato.utils import (
     CheckParameterRulespecWithItem,
-    rulespec_registry,
     RulespecGroupCheckParametersNetworking,
+    rulespec_registry,
 )
 
 
@@ -33,8 +33,8 @@ def _parameter_valuespec_cisco_asyncos_feature_keys():
          Tuple(
              title=_('Levels for feature key expiration in days'),
              elements=[
-                 Integer(title=_('Warning'), default_value=30, unit=_('days before expiration')),
-                 Integer(title=_('Critical'), default_value=7, unit=_('days before expiration')),
+                 Integer(title=_('Warning at'), default_value=30, unit=_('days before expiration')),
+                 Integer(title=_('Critical at'), default_value=7, unit=_('days before expiration')),
              ])),
     ])
 
diff --git a/gui/wato/cisco_asyncos_license.py b/source/gui/wato/cisco_asyncos_license.py
similarity index 87%
rename from gui/wato/cisco_asyncos_license.py
rename to source/gui/wato/cisco_asyncos_license.py
index fb7cbcafd99b6ca5d8484d8aa96f7277a1bf053b..31c4a612ad166fa406a7ed9ae516feb832c8b65c 100644
--- a/gui/wato/cisco_asyncos_license.py
+++ b/source/gui/wato/cisco_asyncos_license.py
@@ -6,15 +6,15 @@ from cmk.gui.i18n import _
 from cmk.gui.valuespec import (
     Dictionary,
     Integer,
-    TextAscii,
     ListOfStrings,
+    TextAscii,
     Tuple,
 )
 
 from cmk.gui.plugins.wato.utils import (
     CheckParameterRulespecWithItem,
-    rulespec_registry,
     RulespecGroupCheckParametersNetworking,
+    rulespec_registry,
 )
 
 
@@ -34,8 +34,8 @@ def _parameter_valuespec_cisco_asyncos_license():
          Tuple(
              title=_('Levels for licence expiration in days'),
              elements=[
-                 Integer(title=_('Warning'), default_value=30, unit=_('days before expiration')),
-                 Integer(title=_('Critical'), default_value=7, unit=_('days before expiration')),
+                 Integer(title=_('Warning at'), default_value=30, unit=_('days before expiration')),
+                 Integer(title=_('Critical at'), default_value=7, unit=_('days before expiration')),
              ])),
     ])
 
diff --git a/gui/wato/cisco_asyncos_queue.py b/source/gui/wato/cisco_asyncos_queue.py
similarity index 82%
rename from gui/wato/cisco_asyncos_queue.py
rename to source/gui/wato/cisco_asyncos_queue.py
index 66d0258affe07442c455d6cf439b270d22551ba3..403a5e5d4dd0d35315a9173e664469b0b0d64ab9 100644
--- a/gui/wato/cisco_asyncos_queue.py
+++ b/source/gui/wato/cisco_asyncos_queue.py
@@ -7,14 +7,13 @@ from cmk.gui.valuespec import (
     Dictionary,
     Integer,
     TextAscii,
-    ListOfStrings,
     Tuple,
 )
 
 from cmk.gui.plugins.wato.utils import (
     CheckParameterRulespecWithItem,
-    rulespec_registry,
     RulespecGroupCheckParametersNetworking,
+    rulespec_registry,
 )
 
 
@@ -24,8 +23,8 @@ def _parameter_valuespec_cisco_asyncos_queue():
          Tuple(
              title=_('Levels for nuber of messages in work queue'),
              elements=[
-                 Integer(title=_('Warning'), default_value=50, unit=_('# of messages')),
-                 Integer(title=_('Critical'), default_value=100, unit=_('# of messages')),
+                 Integer(title=_('Warning at'), default_value=50, unit=_('# of messages')),
+                 Integer(title=_('Critical at'), default_value=100, unit=_('# of messages')),
              ])),
     ])
 
diff --git a/gui/wato/cisco_asyncos_updates.py b/source/gui/wato/cisco_asyncos_updates.py
similarity index 86%
rename from gui/wato/cisco_asyncos_updates.py
rename to source/gui/wato/cisco_asyncos_updates.py
index 4f25b4d3fd07d5edbaf559acbabcca4d62844867..5f1061e5f9d32dcf576fefe0a9821958143b0ce9 100644
--- a/gui/wato/cisco_asyncos_updates.py
+++ b/source/gui/wato/cisco_asyncos_updates.py
@@ -6,15 +6,15 @@ from cmk.gui.i18n import _
 from cmk.gui.valuespec import (
     Dictionary,
     Integer,
-    TextAscii,
     ListOfStrings,
+    TextAscii,
     Tuple,
 )
 
 from cmk.gui.plugins.wato.utils import (
     CheckParameterRulespecWithItem,
-    rulespec_registry,
     RulespecGroupCheckParametersNetworking,
+    rulespec_registry,
 )
 
 
@@ -32,8 +32,8 @@ def _parameter_valuespec_cisco_asyncos_updates():
          Tuple(
              title=_('Levels for failed attempts'),
              elements=[
-                 Integer(title=_('Warning'), default_value=5, unit=_('# of failed attempts')),
-                 Integer(title=_('Critical'), default_value=10, unit=_('# of failed attempts')),
+                 Integer(title=_('Warning at'), default_value=5, unit=_('# of failed attempts')),
+                 Integer(title=_('Critical at'), default_value=10, unit=_('# of failed attempts')),
              ])),
     ])
 
diff --git a/packages/cisco_asyncos b/source/packages/cisco_asyncos
similarity index 89%
rename from packages/cisco_asyncos
rename to source/packages/cisco_asyncos
index 40f791060a0f63ebc586317b40b1f7a2866f7bb9..bd98f08687d2038ec1c6b05dcb8d42f43b27a472 100644
--- a/packages/cisco_asyncos
+++ b/source/packages/cisco_asyncos
@@ -42,10 +42,12 @@
            'gui': ['wato/cisco_asyncos_feature_keys.py',
                    'wato/cisco_asyncos_license.py',
                    'wato/cisco_asyncos_queue.py',
-                   'wato/cisco_asyncos_updates.py']},
+                   'wato/cisco_asyncos_updates.py',
+                   'metrics/cisco_asyncos.py',
+                   'wato/cisco_asyncos_dns.py']},
  'name': 'cisco_asyncos',
  'title': 'Cisco AsyncOS (IronPort) checks',
- 'version': '20230306.v0.3.0',
- 'version.min_required': '2.1.0',
- 'version.packaged': '2.1.0p22',
- 'version.usable_until': None}
\ No newline at end of file
+ 'version': '0.4.1-20240418',
+ 'version.min_required': '2.1.0b1',
+ 'version.packaged': '2.2.0p24',
+ 'version.usable_until': None}