From 9865546e6424ccd05e2e642f8a40c0ab6746b29e Mon Sep 17 00:00:00 2001
From: "th.l" <thl-cmk@outlook.com>
Date: Fri, 27 Sep 2024 12:35:59 +0200
Subject: [PATCH] update project

---
 README.md                                     |   2 +-
 mkp/unbound-1.3.3-20240927.mkp                | Bin 0 -> 10659 bytes
 source/agent_based/unbound.py                 | 207 +------------
 source/agent_based/unbound_answers.py         | 119 ++++++++
 source/agent_based/unbound_cache.py           | 124 ++++++++
 source/agent_based/unbound_status.py          |  77 +++--
 source/agent_based/unbound_type_stats.py      | 289 ++++++++++++++++++
 .../agent_based/unbound_unwanted_replies.py   |  85 ++++++
 source/agent_based/utils/unbound.py           |  17 ++
 .../{unbound.py => unbound_answers.py}        |  39 +--
 source/gui/metrics/unbound_cache.py           |  46 +++
 source/gui/metrics/unbound_type_stats.py      | 209 +++++++++++++
 .../gui/metrics/unbound_unwanted_replies.py   |  66 ++++
 .../{unbound.py => unbound_answers.py}        |  65 +---
 .../wato/check_parameters/unbound_bakery.py   |   7 +-
 .../wato/check_parameters/unbound_cache.py    |  74 +++++
 .../wato/check_parameters/unbound_replies.py  |  66 ++++
 .../wato/check_parameters/unbound_status.py   |  41 +++
 .../check_parameters/unbound_type_stats.py    | 115 +++++++
 source/packages/unbound                       |  28 +-
 20 files changed, 1346 insertions(+), 330 deletions(-)
 create mode 100644 mkp/unbound-1.3.3-20240927.mkp
 create mode 100644 source/agent_based/unbound_answers.py
 create mode 100644 source/agent_based/unbound_cache.py
 create mode 100644 source/agent_based/unbound_type_stats.py
 create mode 100644 source/agent_based/unbound_unwanted_replies.py
 create mode 100644 source/agent_based/utils/unbound.py
 rename source/gui/metrics/{unbound.py => unbound_answers.py} (77%)
 create mode 100644 source/gui/metrics/unbound_cache.py
 create mode 100644 source/gui/metrics/unbound_type_stats.py
 create mode 100644 source/gui/metrics/unbound_unwanted_replies.py
 rename source/gui/wato/check_parameters/{unbound.py => unbound_answers.py} (67%)
 create mode 100644 source/gui/wato/check_parameters/unbound_cache.py
 create mode 100644 source/gui/wato/check_parameters/unbound_replies.py
 create mode 100644 source/gui/wato/check_parameters/unbound_status.py
 create mode 100644 source/gui/wato/check_parameters/unbound_type_stats.py

diff --git a/README.md b/README.md
index 43fe65b..c429494 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-[PACKAGE]: ../../raw/master/mkp/unbound-1.2.6-20240527.mkp "unbound-1.2.6-20240527.mkp"
+[PACKAGE]: ../../raw/master/mkp/unbound-1.3.3-20240927.mkp "unbound-1.3.3-20240927.mkp"
 # unbound
 
 This agent plugin cheks the state of the unbound dns daemon. For more information about unbound see: https://nlnetlabs.nl/projects/unbound/about/
diff --git a/mkp/unbound-1.3.3-20240927.mkp b/mkp/unbound-1.3.3-20240927.mkp
new file mode 100644
index 0000000000000000000000000000000000000000..653151d6d9584aa7a2777cf0180da4cbf9dec470
GIT binary patch
literal 10659
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
Xah3mzzx?-C286b*_KpEb00sF!iFP#m

literal 0
HcmV?d00001

diff --git a/source/agent_based/unbound.py b/source/agent_based/unbound.py
index f37ffe9..879ebd3 100644
--- a/source/agent_based/unbound.py
+++ b/source/agent_based/unbound.py
@@ -25,37 +25,11 @@
 #             added output in case get_rate is initialising (unknown state in discovery)
 #             removed default levels for unbound_answers
 #             added params for unwanted replies
+# 2024-07-24: split unbound.py in separate files per check
 
-
-from time import time as now_time
-from typing import (
-    Any,
-    Mapping,
-)
-
-from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
-    CheckResult,
-    DiscoveryResult,
-    StringTable,
-)
-
-from cmk.base.plugins.agent_based.agent_based_api.v1 import (
-    GetRateError,
-    Result,
-    Service,
-    State,
-    check_levels,
-    get_rate,
-    get_value_store,
-    register,
-    render,
-)
-
-UnboundSection = Mapping[str, int | float]
-
-
-def render_qps(x: float) -> str:
-    return f'{x:.2f}/s'
+from cmk.base.plugins.agent_based.agent_based_api.v1 import register
+from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import StringTable
+from cmk.base.plugins.agent_based.utils.unbound import UnboundSection
 
 
 def parse_unbound(string_table: StringTable) -> UnboundSection:
@@ -65,6 +39,7 @@ def parse_unbound(string_table: StringTable) -> UnboundSection:
             section[key] = int(value)
         except ValueError:
             section[key] = float(value)
+
     return section
 
 
@@ -72,175 +47,3 @@ register.agent_section(
     name='unbound',
     parse_function=parse_unbound,
 )
-
-
-def discover_unbound_cache(section: UnboundSection) -> DiscoveryResult:
-    if 'total.num.cachehits' in section and 'total.num.cachemiss' in section:
-        yield Service()
-
-
-def check_unbound_cache(
-        params: Mapping[str, Any],
-        section: UnboundSection,
-) -> CheckResult:
-    cumulative_cache_hits = section.get('total.num.cachehits')
-    cumulative_cache_miss = section.get('total.num.cachemiss')
-    now = now_time()
-
-    if None in (cumulative_cache_hits, cumulative_cache_miss, now):
-        return
-
-    cache_hits = get_rate(
-        get_value_store(),
-        'unbound_cache_hits',
-        now,
-        cumulative_cache_hits,
-        raise_overflow=True,
-    )
-    cache_miss = get_rate(
-        get_value_store(),
-        'unbound_cache_miss',
-        now,
-        cumulative_cache_miss,
-        raise_overflow=True,
-    )
-    total = cache_hits + cache_miss
-    hit_perc = (cache_hits / float(total)) * 100.0 if total != 0 else 100.0
-
-    yield from check_levels(
-        value=cache_miss,
-        metric_name='cache_misses_rate',
-        levels_upper=params.get('cache_misses'),
-        render_func=render_qps,
-        label='Cache Misses',
-        notice_only=True,
-    )
-
-    yield from check_levels(
-        value=cache_hits,
-        metric_name='cache_hit_rate',
-        render_func=render_qps,
-        label='Cache Hits',
-        notice_only=True,
-    )
-
-    yield from check_levels(
-        value=hit_perc,
-        metric_name='cache_hit_ratio',
-        levels_lower=params.get('cache_hits'),
-        render_func=render.percent,
-        label='Cache Hit Ratio',
-    )
-
-
-register.check_plugin(
-    name='unbound_cache',
-    service_name='Unbound Cache',
-    sections=['unbound'],
-    discovery_function=discover_unbound_cache,
-    check_function=check_unbound_cache,
-    check_default_parameters={},
-    check_ruleset_name='unbound_cache',
-)
-
-
-def discover_unbound_answers(section: UnboundSection) -> DiscoveryResult:
-    if 'num.answer.rcode.SERVFAIL' in section:
-        yield Service()
-
-
-def check_unbound_answers(params: Mapping, section: UnboundSection) -> CheckResult:
-    key_prefix = 'num.answer.rcode.'
-    now = now_time()
-
-    total = sum(
-        value for key, value in section.items()
-        if key.startswith(key_prefix)
-    )
-
-    init_counters = False
-    for key, value in section.items():
-        if not key.startswith(key_prefix):
-            continue
-        answer = key[len(key_prefix):]
-
-        try:
-            rate = get_rate(
-                get_value_store(),
-                f'unbound_answers_{answer}',
-                now,
-                value,
-                raise_overflow=True,
-            )
-        except GetRateError as e:
-            if not init_counters:
-                yield Result(state=State.OK, summary=str(e))
-                init_counters = True
-        else:
-            levels_upper = params.get(f'levels_upper_{answer}')
-            if levels_upper is not None and len(levels_upper) == 3:
-                # levels on the ratio of answers
-                levels_upper = (
-                    levels_upper[0] * total,
-                    levels_upper[1] * total,
-                )
-            yield from check_levels(
-                value=rate,
-                levels_upper=levels_upper,
-                metric_name=f'unbound_answers_{answer}',
-                render_func=render_qps,
-                label=answer,
-                notice_only=f'levels_upper_{answer}' not in params,
-            )
-
-
-register.check_plugin(
-    name='unbound_answers',
-    service_name='Unbound Answers',
-    sections=['unbound'],
-    discovery_function=discover_unbound_answers,
-    check_function=check_unbound_answers,
-    check_default_parameters={
-        # 'levels_upper_NOERROR': (101, 101),
-        # 'levels_upper_SERVFAIL': (10, 100),
-        # 'levels_upper_REFUSED': (10, 100),
-    },
-    check_ruleset_name='unbound_answers',
-)
-
-
-def discover_unbound_unwanted_replies(section: UnboundSection) -> DiscoveryResult:
-    if 'unwanted.replies' in section:
-        yield Service()
-
-
-def check_unbound_unwanted_replies(params, section: UnboundSection) -> CheckResult:
-    if 'unwanted.replies' not in section:
-        return
-
-    rate = get_rate(
-        get_value_store(),
-        'unbound_unwanted_replies',
-        now_time(),
-        section['unwanted.replies'],
-        raise_overflow=True,
-    )
-
-    yield from check_levels(
-        value=rate,
-        levels_upper=params.get('unwanted_replies'),
-        metric_name='unbound_unwanted_replies',
-        render_func=render_qps,
-        label='Unwanted Replies',
-    )
-
-
-register.check_plugin(
-    name='unbound_unwanted_replies',
-    service_name='Unbound Unwanted Replies',
-    sections=['unbound'],
-    discovery_function=discover_unbound_unwanted_replies,
-    check_function=check_unbound_unwanted_replies,
-    check_default_parameters={},
-    check_ruleset_name='unbound_replies',
-)
diff --git a/source/agent_based/unbound_answers.py b/source/agent_based/unbound_answers.py
new file mode 100644
index 0000000..09b5366
--- /dev/null
+++ b/source/agent_based/unbound_answers.py
@@ -0,0 +1,119 @@
+#!/usr/bin/env python3
+
+# Copyright (C) 2022, Jan-Philipp Litza (PLUTEX) <jpl@plutex.de>.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+# https://nlnetlabs.nl/projects/unbound/about/
+
+# changes by thl-cmk[at]outlook[dot]com
+# 2024-04-21: removed Union -> no need "int | float" should do
+#             added levels_upper_NOERROR to default parameters -> show up in info line
+# 2024-05-22: changed time for get_rate from section to system time
+#             added output in case get_rate is initialising (unknown state in discovery)
+#             removed default levels for unbound_answers
+#             added params for unwanted replies
+
+
+from time import time as now_time
+from typing import (
+    Mapping,
+)
+
+from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
+    CheckResult,
+    DiscoveryResult,
+)
+
+from cmk.base.plugins.agent_based.agent_based_api.v1 import (
+    GetRateError,
+    Result,
+    Service,
+    State,
+    check_levels,
+    get_rate,
+    get_value_store,
+    register,
+)
+
+from cmk.base.plugins.agent_based.utils.unbound import (
+    UnboundSection,
+    render_qps,
+)
+
+
+def discover_unbound_answers(section: UnboundSection) -> DiscoveryResult:
+    if 'num.answer.rcode.SERVFAIL' in section:
+        yield Service()
+
+
+def check_unbound_answers(params: Mapping, section: UnboundSection) -> CheckResult:
+    key_prefix = 'num.answer.rcode.'
+    now = now_time()
+
+    total = sum(
+        value for key, value in section.items()
+        if key.startswith(key_prefix)
+    )
+
+    init_counters = False
+    for key, value in section.items():
+        if not key.startswith(key_prefix):
+            continue
+        answer = key[len(key_prefix):]
+
+        try:
+            rate = get_rate(
+                get_value_store(),
+                f'unbound_answers_{answer}',
+                now,
+                value,
+                raise_overflow=True,
+            )
+        except GetRateError as e:
+            if not init_counters:
+                yield Result(state=State.OK, summary=str(e))
+                init_counters = True
+        else:
+            levels_upper = params.get(f'levels_upper_{answer}')
+            if levels_upper is not None and len(levels_upper) == 3:
+                # levels on the ratio of answers
+                levels_upper = (
+                    levels_upper[0] * total,
+                    levels_upper[1] * total,
+                )
+            yield from check_levels(
+                value=rate,
+                levels_upper=levels_upper,
+                metric_name=f'unbound_answers_{answer}',
+                render_func=render_qps,
+                label=answer,
+                notice_only=f'levels_upper_{answer}' not in params,
+            )
+
+
+register.check_plugin(
+    name='unbound_answers',
+    service_name='Unbound Answers',
+    sections=['unbound'],
+    discovery_function=discover_unbound_answers,
+    check_function=check_unbound_answers,
+    check_default_parameters={
+        # 'levels_upper_NOERROR': (101, 101),
+        # 'levels_upper_SERVFAIL': (10, 100),
+        # 'levels_upper_REFUSED': (10, 100),
+    },
+    check_ruleset_name='unbound_answers',
+)
diff --git a/source/agent_based/unbound_cache.py b/source/agent_based/unbound_cache.py
new file mode 100644
index 0000000..995cee3
--- /dev/null
+++ b/source/agent_based/unbound_cache.py
@@ -0,0 +1,124 @@
+#!/usr/bin/env python3
+
+# Copyright (C) 2022, Jan-Philipp Litza (PLUTEX) <jpl@plutex.de>.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+# https://nlnetlabs.nl/projects/unbound/about/
+
+# changes by thl-cmk[at]outlook[dot]com
+# 2024-04-21: removed Union -> no need "int | float" should do
+#             added levels_upper_NOERROR to default parameters -> show up in info line
+# 2024-05-22: changed time for get_rate from section to system time
+#             added output in case get_rate is initialising (unknown state in discovery)
+#             removed default levels for unbound_answers
+#             added params for unwanted replies
+
+
+from time import time as now_time
+from typing import (
+    Any,
+    Mapping,
+)
+
+from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
+    CheckResult,
+    DiscoveryResult,
+)
+
+from cmk.base.plugins.agent_based.agent_based_api.v1 import (
+    Service,
+    check_levels,
+    get_rate,
+    get_value_store,
+    register,
+    render,
+)
+
+from cmk.base.plugins.agent_based.utils.unbound import (
+    UnboundSection,
+    render_qps,
+)
+
+
+def discover_unbound_cache(section: UnboundSection) -> DiscoveryResult:
+    if 'total.num.cachehits' in section and 'total.num.cachemiss' in section:
+        yield Service()
+
+
+def check_unbound_cache(
+        params: Mapping[str, Any],
+        section: UnboundSection,
+) -> CheckResult:
+    cumulative_cache_hits = section.get('total.num.cachehits')
+    cumulative_cache_miss = section.get('total.num.cachemiss')
+    now = now_time()
+
+    if None in (cumulative_cache_hits, cumulative_cache_miss, now):
+        return
+
+    cache_hits = get_rate(
+        get_value_store(),
+        'unbound_cache_hits',
+        now,
+        cumulative_cache_hits,
+        raise_overflow=True,
+    )
+    cache_miss = get_rate(
+        get_value_store(),
+        'unbound_cache_miss',
+        now,
+        cumulative_cache_miss,
+        raise_overflow=True,
+    )
+    total = cache_hits + cache_miss
+    hit_perc = (cache_hits / float(total)) * 100.0 if total != 0 else 100.0
+
+    yield from check_levels(
+        value=cache_miss,
+        metric_name='cache_misses_rate',
+        levels_upper=params.get('cache_misses'),
+        render_func=render_qps,
+        label='Cache Misses',
+        notice_only=True,
+    )
+
+    yield from check_levels(
+        value=cache_hits,
+        metric_name='cache_hit_rate',
+        render_func=render_qps,
+        label='Cache Hits',
+        notice_only=True,
+    )
+
+    yield from check_levels(
+        value=hit_perc,
+        metric_name='cache_hit_ratio',
+        levels_lower=params.get('cache_hits'),
+        render_func=render.percent,
+        label='Cache Hit Ratio',
+    )
+
+
+register.check_plugin(
+    name='unbound_cache',
+    service_name='Unbound Cache',
+    sections=['unbound'],
+    discovery_function=discover_unbound_cache,
+    check_function=check_unbound_cache,
+    check_default_parameters={},
+    check_ruleset_name='unbound_cache',
+)
+
diff --git a/source/agent_based/unbound_status.py b/source/agent_based/unbound_status.py
index 3cc9941..08d89ef 100644
--- a/source/agent_based/unbound_status.py
+++ b/source/agent_based/unbound_status.py
@@ -8,13 +8,12 @@
 # Date  : 2024-05-21
 # File  : unbound_status.py
 
-from dataclasses import dataclass
+# 2024-09-10: fixed crash if unbound is not running
 
-from typing import (
-    Any,
-    Mapping,
-    Sequence
-)
+from collections.abc import Mapping, Sequence
+from dataclasses import dataclass
+from re import match as re_match
+from typing import Any
 
 from cmk.utils import debug
 from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
@@ -24,28 +23,38 @@ from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
 )
 
 from cmk.base.plugins.agent_based.agent_based_api.v1 import (
+    Result,
     Service,
+    State,
     check_levels,
     register,
     render,
-    Result,
-    State,
 )
 
 
 @dataclass(frozen=True)
 class UnboundStatus:
-    version: str
-    verbosity: int
-    threads: int
-    modules: Sequence[str]
-    uptime: int
-    options: Sequence[str]
     status: str
-    pid: int
+    modules: Sequence[str] | None = None
+    options: Sequence[str] | None = None
+    pid: int | None = None
+    threads: int | None = None
+    uptime: int | None = None
+    verbosity: int | None = None
+    version: str | None = None
 
     @classmethod
     def parse(cls, string_table: StringTable):
+        # set defaults
+        modules = None
+        options = None
+        pid = None
+        status = None
+        threads = None
+        uptime = None
+        verbosity = None
+        version = None
+
         for line in string_table:
             key, value = line[0].split(' ', 1)
             key = key.rstrip(':')
@@ -67,25 +76,30 @@ class UnboundStatus:
                     options = value.split(' ')
                 case 'unbound':
                     # (pid 520) is running...
-                    pid = int(value.split(') ')[0].replace('(pid ', ''))
-                    status = value.split(') ', -1)[-1].strip('.')
+                    # is stopped
+                    re_pid = r'\(pid (\d+)\).*'
+                    re_status = r'.*(is \w+)'
+                    if pid := re_match(re_pid, value):
+                        pid = pid[1]
+                    if status := re_match(re_status, value):
+                        status = status[1]
                 case _:
                     pass
 
         try:
             return cls(
-                version=version,
-                verbosity=verbosity,
-                threads=threads,
                 modules=modules,
-                uptime=uptime,
                 options=options,
                 pid=pid,
                 status=status,
+                threads=threads,
+                uptime=uptime,
+                verbosity=verbosity,
+                version=version,
             )
         except NameError as e:
             if debug.enabled:
-                print(f'name error {e}')
+                print(f'name error: {e}')
             return
 
 
@@ -100,20 +114,25 @@ register.agent_section(
 
 
 def discover_unbound_status(section: UnboundStatus) -> DiscoveryResult:
+    print(section)
     yield Service()
 
 
 # UnboundStatus(
-#     version='1.13.1',
-#     verbosity=0,
-#     threads=1,
 #     modules=['subnet', 'validator', 'iterator'],
-#     uptime=1759,
 #     options=['reuseport', 'control(ssl)'],
-#     status='(pid 520) is running',
 #     pid=520
+#     status='(pid 520) is running',
+#     threads=1,
+#     uptime=1759,
+#     verbosity=0,
+#     version='1.13.1',
 # )
 def check_unbound_status(params: Mapping[str, Any], section: UnboundStatus, ) -> CheckResult:
+    if section.status != 'is running':
+        yield Result(state=State(params['status_not_running']), summary=f'Status: {section.status}')
+        return
+
     yield Result(state=State.OK, summary=f'Status: {section.status}')
     yield from check_levels(
         value=section.uptime,
@@ -129,6 +148,8 @@ register.check_plugin(
     service_name="Unbound",
     discovery_function=discover_unbound_status,
     check_function=check_unbound_status,
-    check_default_parameters={},
+    check_default_parameters={
+        'status_not_running': 2
+    },
     check_ruleset_name="unbound_status",
 )
diff --git a/source/agent_based/unbound_type_stats.py b/source/agent_based/unbound_type_stats.py
new file mode 100644
index 0000000..2e3af1b
--- /dev/null
+++ b/source/agent_based/unbound_type_stats.py
@@ -0,0 +1,289 @@
+#!/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-05-21
+# File  : unbound_type_stats.py
+
+# based on the unbound plugin by Jan-Philipp Litza (PLUTEX) <jpl@plutex.de>.
+# and the work of ruettimann[at]init7[dot]net
+
+# https://nlnetlabs.nl/projects/unbound/about/
+
+from collections.abc import Mapping, Sequence, MutableSequence
+from time import time as now_time
+from typing import (
+    List,
+    Tuple,
+)
+
+from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
+    CheckResult,
+    DiscoveryResult,
+)
+
+from cmk.base.plugins.agent_based.agent_based_api.v1 import (
+    GetRateError,
+    Result,
+    Service,
+    State,
+    check_levels,
+    get_rate,
+    get_value_store,
+    register,
+)
+
+from cmk.base.plugins.agent_based.utils.unbound import (
+    UnboundSection,
+    render_qps,
+)
+
+# https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml
+ANSWER_TYPES_MOST_COMMON: List[str] = [
+    'A',
+    'AAAA',
+    'ANY',
+    'CNAME',
+    'DNSKEY',
+    'DS',
+    'HINFO',
+    'HTTPS',
+    'MX',
+    'NAPTR',
+    'NS',
+    'PTR',
+    'RRSIG',
+    'SOA',
+    'SRV',
+    'SVCB',
+    'TXT',
+    # 'TYPE65',  # in summary for HTTPS
+]
+ANSWER_TYPES_COMMON: List[str] = [
+    'AFSDB',
+    'APL',
+    'CAA',
+    'CDNSKEY',
+    'CDS',
+    'CERT',
+    'CSYNC',
+    'DHCID',
+    'DLV',
+    'DNAME',
+    'EUI48',
+    'EUI64',
+    'HIP',
+    'IPSECKEY',
+    'KEY',
+    'KX',
+    'LOC',
+    'NSEC',
+    'NSEC3',
+    'NSEC3PARAM',
+    'OPENPGPKEY',
+    'RP',
+    'SIG',
+    'SMIMEA',
+    'SSHFP',
+    'TA',
+    'TKEY',
+    'TLSA',
+    'TSIG',
+    'URI',
+    'WALLET',
+    'ZONEMD',
+]
+ANSWER_TYPES_OBSOLETE: List[str] = [
+    'A6',
+    'ATMA',
+    'DOA',
+    'EID',
+    'GID',
+    'GPOS',
+    'ISDN',
+    'L32',
+    'L64',
+    'LP',
+    'MAILA',
+    'MAILB',
+    'MB',
+    'MD',
+    'MF',
+    'MG',
+    'MINFO',
+    'MR',
+    'NB',
+    'NBSTAT',
+    'NID',
+    'NIMLOC',
+    'NINFO',
+    'NSAP',
+    'NSAP-PTR',
+    'NULL',
+    'NXT',
+    'PX',
+    'RKEY',
+    'RT',
+    'SINK',
+    'SPF',
+    'TALINK',
+    'UID',
+    'UINFO',
+    'UNSPEC',
+    'WKS',
+    'X25',
+]
+
+ANSWER_TYPES: List[str] = ANSWER_TYPES_MOST_COMMON + ANSWER_TYPES_COMMON + ANSWER_TYPES_OBSOLETE
+
+SUM_TYPES: Mapping[str: Sequence[str]] = {
+    'AAAA': ['AAAA', 'A6'],
+    'HTTPS': ['HTTPS', 'TYPE65'],
+}
+
+
+def discover_unbound_type_stats(section: UnboundSection) -> DiscoveryResult:
+    if 'num.query.type.A' in section:
+        yield Service()
+
+
+def yield_answer(
+        answer: str,
+        levels_upper: Tuple[int, int] | None,
+        total: float,
+        value: float,
+        notice: bool = True,
+        metric: bool = True,
+):
+    now = now_time()
+    try:
+        rate = get_rate(
+            get_value_store(),
+            f'unbound_type_stats_{answer}',
+            now,
+            value,
+            raise_overflow=True,
+        )
+    except GetRateError as e:
+        yield Result(state=State.OK, notice=str(e))
+        return
+    else:
+        # if rate == 0:
+        #     return
+        if levels_upper is not None and len(levels_upper) == 3:
+            # levels on the ratio of answers
+            levels_upper = (
+                levels_upper[0] * total,
+                levels_upper[1] * total,
+            )
+        # if levels_upper:
+        #     notice = False
+        metric_name = f'unbound_type_stats_{answer.replace("-", "_").replace(" ", "_")}' if metric else None
+        yield from check_levels(
+            value=rate,
+            levels_upper=levels_upper,
+            metric_name=metric_name,
+            render_func=render_qps,
+            label=answer,
+            notice_only=notice,
+        )
+
+
+# ToDo: values are converted to answers/s, total is an absolute value -> should be changed to rate/1?
+def check_unbound_type_stats(params: Mapping, section: UnboundSection) -> CheckResult:
+    key_prefix = 'num.query.type.'
+    total_common: float = 0.0
+    total_obsolete: float = 0.0
+    other_value: float = 0.0
+    other_types_found: MutableSequence[str] = []
+
+    for sum_type in SUM_TYPES:
+        summary = 0.0
+        for answer_type in SUM_TYPES[sum_type]:
+            key = f'{key_prefix}{answer_type}'
+            summary += section.get(key, 0.0)
+            try:
+                section.pop(key)
+            except KeyError:
+                pass
+
+        if summary > 0.0:
+            section.update({f'{key_prefix}{sum_type}': summary})
+
+    section = dict(sorted(section.items()))
+
+    total = sum(
+        value for key, value in section.items()
+        if key.startswith(key_prefix)
+    )
+
+    for key, value in section.items():
+        if not key.startswith(key_prefix):
+            continue
+        answer = key[len(key_prefix):]
+
+        if answer in ANSWER_TYPES:
+            if answer in ANSWER_TYPES_COMMON:
+                total_common += value
+            elif answer in ANSWER_TYPES_OBSOLETE:  #
+                total_obsolete += value
+
+            yield from yield_answer(
+                answer=answer,
+                total=total,
+                value=value,
+                levels_upper=params.get(f'levels_upper_{answer}')
+            )
+        else:
+            other_types_found.append(answer)
+            other_value += value
+            yield from yield_answer(
+                answer=answer,
+                total=total,
+                value=value,
+                levels_upper=None,
+                metric=False,
+            )
+
+    if other_types_found:
+        answer = 'others'
+        yield from yield_answer(
+            answer=answer,
+            total=total,
+            value=other_value,
+            levels_upper=params.get(f'levels_upper_{answer}')
+        )
+        answer_types = ', '.join(other_types_found)
+        yield Result(
+            state=State(params.get('state_other_type', 0)),
+            notice=f'Other DNS answer typ(e) found: {answer_types}'
+        )
+
+    for answer, value in [
+        ('Total', total),
+        ('Total_common', total_common),
+        ('Total_obsolete', total_obsolete),
+    ]:
+        if value > 0:
+            yield from yield_answer(
+                answer=answer,
+                total=total,
+                value=value,
+                levels_upper=params.get(f'levels_upper_{answer}'),
+                notice=False,
+            )
+
+
+register.check_plugin(
+    name='unbound_type_stats',
+    service_name='Unbound Type Stats',
+    sections=['unbound'],
+    discovery_function=discover_unbound_type_stats,
+    check_function=check_unbound_type_stats,
+    check_default_parameters={
+    },
+    check_ruleset_name='unbound_type_stats',
+)
diff --git a/source/agent_based/unbound_unwanted_replies.py b/source/agent_based/unbound_unwanted_replies.py
new file mode 100644
index 0000000..2c9b207
--- /dev/null
+++ b/source/agent_based/unbound_unwanted_replies.py
@@ -0,0 +1,85 @@
+#!/usr/bin/env python3
+
+# Copyright (C) 2022, Jan-Philipp Litza (PLUTEX) <jpl@plutex.de>.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+# https://nlnetlabs.nl/projects/unbound/about/
+
+# changes by thl-cmk[at]outlook[dot]com
+# 2024-04-21: removed Union -> no need "int | float" should do
+#             added levels_upper_NOERROR to default parameters -> show up in info line
+# 2024-05-22: changed time for get_rate from section to system time
+#             added output in case get_rate is initialising (unknown state in discovery)
+#             removed default levels for unbound_answers
+#             added params for unwanted replies
+
+
+from time import time as now_time
+
+from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
+    CheckResult,
+    DiscoveryResult,
+)
+
+from cmk.base.plugins.agent_based.agent_based_api.v1 import (
+    Service,
+    check_levels,
+    get_rate,
+    get_value_store,
+    register,
+)
+
+from cmk.base.plugins.agent_based.utils.unbound import (
+    UnboundSection,
+    render_qps,
+)
+
+
+def discover_unbound_unwanted_replies(section: UnboundSection) -> DiscoveryResult:
+    if 'unwanted.replies' in section:
+        yield Service()
+
+
+def check_unbound_unwanted_replies(params, section: UnboundSection) -> CheckResult:
+    if 'unwanted.replies' not in section:
+        return
+
+    rate = get_rate(
+        get_value_store(),
+        'unbound_unwanted_replies',
+        now_time(),
+        section['unwanted.replies'],
+        raise_overflow=True,
+    )
+
+    yield from check_levels(
+        value=rate,
+        levels_upper=params.get('unwanted_replies'),
+        metric_name='unbound_unwanted_replies',
+        render_func=render_qps,
+        label='Unwanted Replies',
+    )
+
+
+register.check_plugin(
+    name='unbound_unwanted_replies',
+    service_name='Unbound Unwanted Replies',
+    sections=['unbound'],
+    discovery_function=discover_unbound_unwanted_replies,
+    check_function=check_unbound_unwanted_replies,
+    check_default_parameters={},
+    check_ruleset_name='unbound_replies',
+)
diff --git a/source/agent_based/utils/unbound.py b/source/agent_based/utils/unbound.py
new file mode 100644
index 0000000..4833a50
--- /dev/null
+++ b/source/agent_based/utils/unbound.py
@@ -0,0 +1,17 @@
+# License: GNU General Public License v2
+#
+# Author: thl-cmk[at]outlook[dot]com
+# URL   : https://thl-cmk.hopto.org
+# Date  : 2024-07-12
+# File  : plugins/agent_based/utils/unbound.py
+
+
+from collections.abc import MutableMapping
+
+UnboundSection = MutableMapping[str, int | float]
+
+
+def render_qps(x: float) -> str:
+    return f'{x:.2f}/s'
+
+
diff --git a/source/gui/metrics/unbound.py b/source/gui/metrics/unbound_answers.py
similarity index 77%
rename from source/gui/metrics/unbound.py
rename to source/gui/metrics/unbound_answers.py
index deec35d..a9bddfb 100644
--- a/source/gui/metrics/unbound.py
+++ b/source/gui/metrics/unbound_answers.py
@@ -77,7 +77,15 @@ graph_info['unbound_answers'] = {
     'title': _('Rate of answers'),
     'metrics': [
         (f'unbound_answers_{answer}', 'line')
-        for answer in ('NOERROR', 'FORMERR', 'SERVFAIL', 'NXDOMAIN', 'NOTIMPL', 'REFUSED', 'nodata')
+        for answer in (
+            'FORMERR',
+            'NOERROR',
+            'NOTIMPL',
+            'NXDOMAIN',
+            'REFUSED',
+            'SERVFAIL',
+            'nodata',
+        )
     ],
 }
 
@@ -95,32 +103,3 @@ perfometer_info.append(('stacked', [
         'exponent': 2,
     },
 ]))
-
-metric_info['cache_hit_rate'] = {
-    'title': _('Cache hits per second'),
-    'unit': '1/s',
-    'color': '26/a',
-}
-
-graph_info['cache_hit_misses'] = {
-    'title': _('Cache Hits and Misses'),
-    'metrics': [('cache_hit_rate', 'line'), ('cache_misses_rate', 'line')],
-}
-
-metric_info['unbound_unwanted_replies'] = {
-    'title': _('Unwanted replies'),
-    'unit': '1/s',
-    'color': '26/a',
-}
-
-graph_info['unbound_unwanted_replies'] = {
-    'title': _('Unwanted replies'),
-    'metrics': [('unbound_unwanted_replies', 'area')],
-}
-
-perfometer_info.append({
-    'type': 'logarithmic',
-    'metric': 'unbound_unwanted_replies',
-    'half_value': 100.0,  # ome year
-    'exponent': 2,
-})
diff --git a/source/gui/metrics/unbound_cache.py b/source/gui/metrics/unbound_cache.py
new file mode 100644
index 0000000..d485617
--- /dev/null
+++ b/source/gui/metrics/unbound_cache.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python3
+# -*- encoding: utf-8; py-indent-offset: 4 -*-
+
+# Copyright (C) 2022, Jan-Philipp Litza (PLUTEX) <jpl@plutex.de>.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+# modifications by thl-cmk[at]outlook[dot]com
+# 2024-02-21: changed import path form mk.gui.plugins.metrics to mk.gui.plugins.metrics.utils
+#             added metrics/graph for unbound_unwanted_replies
+#             moved to ~/local/lib/check_mk/gui/plugins/metrics (from local/share/check_mk/web/plugins/metrics)
+#             added perfometer for unbound_answers (NOERROR/SERVFAIL) and unbound_unwanted_replies
+
+from cmk.gui.i18n import _
+
+from cmk.gui.plugins.metrics.utils import (
+    metric_info,
+    graph_info,
+    perfometer_info,
+)
+
+metric_info['cache_hit_rate'] = {
+    'title': _('Cache hits per second'),
+    'unit': '1/s',
+    'color': '26/a',
+}
+
+graph_info['cache_hit_misses'] = {
+    'title': _('Cache Hits and Misses'),
+    'metrics': [
+        ('cache_hit_rate', 'line'),
+        ('cache_misses_rate', 'line'),
+    ],
+}
diff --git a/source/gui/metrics/unbound_type_stats.py b/source/gui/metrics/unbound_type_stats.py
new file mode 100644
index 0000000..b28b17d
--- /dev/null
+++ b/source/gui/metrics/unbound_type_stats.py
@@ -0,0 +1,209 @@
+#!/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-08-02
+# File  : unbound_type_stats.py
+
+# based on the work of ruettimann[at]init7[dot]net
+
+# https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml
+
+# 2024-08-02: optimized answer_types and metrics creation
+# 2024-08-18: added common and obsolete answer types
+# 2024-08-27: removed duplicate answer types
+#             added METRIC_TYPE and ADD-TOTAL options
+
+from collections.abc import Mapping
+from typing import Final
+from cmk.gui.i18n import _
+
+from cmk.gui.plugins.metrics.utils import (
+    check_metrics,
+    graph_info,
+    metric_info,
+    perfometer_info,
+)
+
+METRIC_TYPE: Final[str] = 'stack'  # can be stack or line
+ADD_TOTAL: Final[bool] = True
+
+answer_types_most_common: Mapping[str, str] = {
+    'Total': '26/a',
+    'Total common': '26/b',
+    'Total obsolete': '16/b',
+    'A': '11/a',
+    'AAAA': '21/a',
+    'ANY': '31/a',
+    'CNAME': '41/a',
+    'DNSKEY': '12/a',
+    'DS': '22/a',
+    'HINFO': '24/a',
+    'HTTPS': '15/a',
+    'MX': '32/a',
+    'NAPTR': '42/a',
+    'NS': '13/a',
+    'PTR': '23/a',
+    'RRSIG': '34/a',
+    'SOA': '33/a',
+    'SRV': '43/a',
+    'SVCB': '44/a',
+    'TXT': '14/a',
+    'others': '36/a',
+}
+
+# https://en.wikipedia.org/wiki/List_of_DNS_record_types
+answer_types_common: Mapping[str, str] = {
+    'AFSDB': '11/a',
+    'APL': '12/a',
+    'CAA': '13/a',
+    'CDNSKEY': '14/a',
+    'CDS': '15/a',
+    'CERT': '16/a',
+    'CSYNC': '21/a',
+    'DHCID': '22/a',
+    'DLV': '23/a',
+    'DNAME': '24/a',
+    'EUI48': '25/a',
+    'EUI64': '31/a',
+    'HIP': '32/a',
+    'IPSECKEY': '33/a',
+    'KEY': '34/a',
+    'KX': '35/a',
+    'LOC': '36/a',
+    'NSEC': '42/a',
+    'NSEC3': '43/a',
+    'NSEC3PARAM': '44/a',
+    'OPENPGPKEY': '45/a',
+    'RP': '46/a',
+    'SIG': '11/b',
+    'SMIMEA': '12/b',
+    'SSHFP': '13/b',
+    'TA': '14/b',
+    'TKEY': '15/b',
+    'TLSA': '16/b',
+    'TSIG': '21/b',
+    'URI': '22/b',
+    'WALLET': '23/b',
+    'ZONEMD': '24/b',
+}
+
+# https://en.wikipedia.org/wiki/List_of_DNS_record_types
+answer_types_obsolete: Mapping[str, str] = {
+    'MD': '11/b',
+    'MF': '12/b',
+    'MAILA': '13/b',
+    'MB': '14/b',
+    'MG': '15/b',
+    'MR': '16/b',
+    'MINFO': '21/b',
+    'MAILB': '22/b',
+    'WKS': '23/b',
+    'NB': '24/b',
+    'NBSTAT': '25/b',
+    'NULL': '26/b',
+    'A6': '31/b',
+    'NXT': '32/b',
+    'X25': '41/b',
+    'ISDN': '42/b',
+    'RT': '43/b',
+    'NSAP': '44/b',
+    'NSAP-PTR': '45/b',
+    'PX': '46/b',
+    'EID': '11/a',
+    'NIMLOC': '12/a',
+    'ATMA': '13/a',
+    'SINK': '15/a',
+    'GPOS': '16/a',
+    'UINFO': '21/a',
+    'UID': '22/a',
+    'GID': '23/a',
+    'UNSPEC': '24/a',
+    'SPF': '25/a',
+    'NINFO': '26/a',
+    'RKEY': '31/a',
+    'TALINK': '32/a',
+    'NID': '33/qa',
+    'L32': '34/a',
+    'L64': '35/a',
+    'LP': '36/a',
+    'DOA': '41/a',
+}
+
+check_metrics["check_mk-unbound_type_stats"] = {}
+
+
+def clean_metric(answer: str) -> str:
+    return f'unbound_type_stats_{answer.replace(" ", "_").replace("-", "_")}'
+
+
+for answer_type, color in answer_types_most_common.items():
+    metric_info[clean_metric(answer_type)] = {
+        'title': _(f'Rate of {answer_type} type stats'),
+        'unit': '1/s',
+        'color': color,
+    }
+    check_metrics["check_mk-unbound_type_stats"].update({clean_metric(answer_type): {"auto_graph": False}})
+
+for answer_type, color in answer_types_common.items():
+    metric_info[clean_metric(answer_type)] = {
+        'title': _(f'Rate of {answer_type} type stats'),
+        'unit': '1/s',
+        'color': color,
+    }
+    check_metrics["check_mk-unbound_type_stats"].update({clean_metric(answer_type): {"auto_graph": False}})
+
+for answer_type, color in answer_types_obsolete.items():
+    metric_info[clean_metric(answer_type)] = {
+        'title': _(f'Rate of {answer_type} type stats'),
+        'unit': '1/s',
+        'color': color,
+    }
+    check_metrics["check_mk-unbound_type_stats"].update({clean_metric(answer_type): {"auto_graph": False}})
+
+graph_info['unbound_type_stats'] = {
+    'title': _('Rate of type stats'),
+    'metrics': [(
+        clean_metric(answer), METRIC_TYPE) for answer in sorted(list(answer_types_most_common.keys())[3:], reverse=True)
+    ],
+    'optional_metrics': [clean_metric(answer) for answer in answer_types_most_common]
+}
+
+graph_info['unbound_type_stats_common'] = {
+    'title': _('Rate of type stats (common)'),
+    'metrics': [(
+        clean_metric(answer), METRIC_TYPE) for answer in sorted(list(answer_types_common.keys()), reverse=True)
+    ],
+    'optional_metrics': [clean_metric(answer) for answer in answer_types_common]
+}
+
+graph_info['unbound_type_stats_obsolete'] = {
+    'title': _('Rate of type stats (obsolete)'),
+    'metrics': [(
+        clean_metric(answer), METRIC_TYPE) for answer in sorted(list(answer_types_obsolete.keys()), reverse=True)
+    ],
+    'optional_metrics': [clean_metric(answer) for answer in answer_types_obsolete]
+}
+
+if ADD_TOTAL:
+    graph_info['unbound_type_stats']['metrics'] = graph_info['unbound_type_stats']['metrics'] + [
+        (f'unbound_type_stats_Total_obsolete', METRIC_TYPE),
+        (f'unbound_type_stats_Total_common', METRIC_TYPE),
+        (f'unbound_type_stats_Total', 'line'),
+    ]
+    graph_info['unbound_type_stats_common']['metrics'] = graph_info['unbound_type_stats_common']['metrics'] + [
+        (f'unbound_type_stats_Total_common', 'line')]
+    graph_info['unbound_type_stats_obsolete']['metrics'] = graph_info['unbound_type_stats_obsolete']['metrics'] + [
+        (f'unbound_type_stats_Total_obsolete', 'line')]
+
+perfometer_info.append(
+    {
+        'type': 'logarithmic',
+        'metric': 'unbound_type_stats_Total',
+        'half_value': 100.0,
+        'exponent': 2,
+    }
+)
diff --git a/source/gui/metrics/unbound_unwanted_replies.py b/source/gui/metrics/unbound_unwanted_replies.py
new file mode 100644
index 0000000..26ad2d4
--- /dev/null
+++ b/source/gui/metrics/unbound_unwanted_replies.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python3
+# -*- encoding: utf-8; py-indent-offset: 4 -*-
+
+# Copyright (C) 2022, Jan-Philipp Litza (PLUTEX) <jpl@plutex.de>.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+# modifications by thl-cmk[at]outlook[dot]com
+# 2024-02-21: changed import path form mk.gui.plugins.metrics to mk.gui.plugins.metrics.utils
+#             added metrics/graph for unbound_unwanted_replies
+#             moved to ~/local/lib/check_mk/gui/plugins/metrics (from local/share/check_mk/web/plugins/metrics)
+#             added perfometer for unbound_answers (NOERROR/SERVFAIL) and unbound_unwanted_replies
+
+from cmk.gui.i18n import _
+
+from cmk.gui.plugins.metrics.utils import (
+    metric_info,
+    graph_info,
+    perfometer_info,
+)
+
+metric_info['cache_hit_rate'] = {
+    'title': _('Cache hits per second'),
+    'unit': '1/s',
+    'color': '26/a',
+}
+
+graph_info['cache_hit_misses'] = {
+    'title': _('Cache Hits and Misses'),
+    'metrics': [
+        ('cache_hit_rate', 'line'),
+        ('cache_misses_rate', 'line'),
+    ],
+}
+
+metric_info['unbound_unwanted_replies'] = {
+    'title': _('Unwanted replies'),
+    'unit': '1/s',
+    'color': '26/a',
+}
+
+graph_info['unbound_unwanted_replies'] = {
+    'title': _('Unwanted replies'),
+    'metrics': [
+        ('unbound_unwanted_replies', 'area')
+    ],
+}
+
+perfometer_info.append({
+    'type': 'logarithmic',
+    'metric': 'unbound_unwanted_replies',
+    'half_value': 100.0,  # ome year
+    'exponent': 2,
+})
diff --git a/source/gui/wato/check_parameters/unbound.py b/source/gui/wato/check_parameters/unbound_answers.py
similarity index 67%
rename from source/gui/wato/check_parameters/unbound.py
rename to source/gui/wato/check_parameters/unbound_answers.py
index f20bb6e..457d896 100644
--- a/source/gui/wato/check_parameters/unbound.py
+++ b/source/gui/wato/check_parameters/unbound_answers.py
@@ -44,38 +44,6 @@ from cmk.gui.valuespec import (
 )
 
 
-def _parameter_valuespec_unbound_cache():
-    return Dictionary(
-        title=_('Unbound: Cache'),
-        elements=[
-            ('cache_misses',
-             Tuple(
-                 title='Levels on cache misses per second',
-                 elements=[
-                     Float(title='warn', ),
-                     Float(title='crit', ),
-                 ])),
-            ('cache_hits',
-             Tuple(
-                 title='Lower levels for hits in %',
-                 elements=[
-                     Percentage(title='warn', ),
-                     Percentage(title='crit', ),
-                 ])),
-        ])
-
-
-rulespec_registry.register(
-    CheckParameterRulespecWithoutItem(
-        check_group_name='unbound_cache',
-        group=RulespecGroupCheckParametersApplications,
-        match_type='dict',
-        parameter_valuespec=_parameter_valuespec_unbound_cache,
-        title=lambda: _('Unbound Cache'),
-    )
-)
-
-
 def _parameter_valuespec_unbound_answers():
     return Dictionary(
         title=_('Unbound answers'),
@@ -91,6 +59,7 @@ def _parameter_valuespec_unbound_answers():
                              Float(title=_('Critical at'), unit=_('qps')),
                          ],
                          title=_('Upper levels in qps'),
+                         orientation='horizontal',
                      ),
                      Tuple(
                          elements=[
@@ -99,15 +68,16 @@ def _parameter_valuespec_unbound_answers():
                              FixedValue(value='%', totext=''),  # needed to decide between both variants
                          ],
                          title=_('Upper levels in %'),
+                         orientation='horizontal',
                      ),
                  ]))
             for answer in (
-                'NOERROR',
                 'FORMERR',
-                'SERVFAIL',
-                'NXDOMAIN',
+                'NOERROR',
                 'NOTIMPL',
+                'NXDOMAIN',
                 'REFUSED',
+                'SERVFAIL',
                 'nodata',
             )
         ],
@@ -123,28 +93,3 @@ rulespec_registry.register(
         title=lambda: _('Unbound Answers'),
     )
 )
-
-
-def _parameter_valuespec_unbound_replies():
-    return Dictionary(
-        title=_('Unbound: Replies'),
-        elements=[
-            ('unwanted_replies',
-             Tuple(
-                 title='Levels on unwanted replies per second',
-                 elements=[
-                     Float(title='warn', ),
-                     Float(title='crit', ),
-                 ])),
-        ])
-
-
-rulespec_registry.register(
-    CheckParameterRulespecWithoutItem(
-        check_group_name='unbound_replies',
-        group=RulespecGroupCheckParametersApplications,
-        match_type='dict',
-        parameter_valuespec=_parameter_valuespec_unbound_replies,
-        title=lambda: _('Unbound Replies'),
-    )
-)
diff --git a/source/gui/wato/check_parameters/unbound_bakery.py b/source/gui/wato/check_parameters/unbound_bakery.py
index d42570f..24f04cb 100644
--- a/source/gui/wato/check_parameters/unbound_bakery.py
+++ b/source/gui/wato/check_parameters/unbound_bakery.py
@@ -8,13 +8,14 @@
 # Date  : unbound_bakery.py
 # File  : unbound_bakery.py
 
-# modifications by thl-cmk[at]outlook[dot]com
 # 2024-04-21: fixed missing FixedValue in import
-#             changed Alternative titles to "Upper levels in qps"/"Upper levels in %" to better differentiate between them
+#             changed Alternative titles to "Upper levels in qps"/"Upper levels in %"
+#             to better differentiate between them
 #             added explicit unit "%2
 #             added ruleset for bakery
 #             renamed to unbound.py (from unbound_parameters.py)
-#             moved to ~/local/lib/check_mk/gui/plugins/wato/check_parameters (from local/share/check_mk/web/plugins/wato)
+#             moved to ~/local/lib/check_mk/gui/plugins/wato/check_parameters
+#                   from local/share/check_mk/web/plugins/wato
 # 2024-05-14: separated WATO for bakery and check in two files
 # 2024-05-27: fixed crash in CRE version (has no cee elements)
 
diff --git a/source/gui/wato/check_parameters/unbound_cache.py b/source/gui/wato/check_parameters/unbound_cache.py
new file mode 100644
index 0000000..51f6f5b
--- /dev/null
+++ b/source/gui/wato/check_parameters/unbound_cache.py
@@ -0,0 +1,74 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2022, Jan-Philipp Litza (PLUTEX) <jpl@plutex.de>.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+# modifications by thl-cmk[at]outlook[dot]com
+# 2024-04-21: fixed missing FixedValue in import
+#             changed Alternative titles to "Upper levels in qps"/"Upper levels in %" to better differentiate between them
+#             added explicit unit "%2
+#             added ruleset for bakery
+#             renamed to unbound.py (from unbound_parameters.py)
+#             moved to ~/local/lib/check_mk/gui/plugins/wato/check_parameters (from local/share/check_mk/web/plugins/wato)
+# 2024-05-14: separated WATO for bakery and check in two files
+# 2024-05-22: added ruleset for unwanted replies
+
+from cmk.gui.i18n import _
+from cmk.gui.plugins.wato.utils import (
+    CheckParameterRulespecWithoutItem,
+    RulespecGroupCheckParametersApplications,
+    rulespec_registry,
+)
+
+from cmk.gui.valuespec import (
+    Dictionary,
+    Float,
+    Percentage,
+    Tuple,
+)
+
+
+def _parameter_valuespec_unbound_cache():
+    return Dictionary(
+        title=_('Unbound: Cache'),
+        elements=[
+            ('cache_misses',
+             Tuple(
+                 title='Levels on cache misses per second',
+                 elements=[
+                     Float(title='warn', ),
+                     Float(title='crit', ),
+                 ])),
+            ('cache_hits',
+             Tuple(
+                 title='Lower levels for hits in %',
+                 elements=[
+                     Percentage(title='warn', ),
+                     Percentage(title='crit', ),
+                 ])),
+        ])
+
+
+rulespec_registry.register(
+    CheckParameterRulespecWithoutItem(
+        check_group_name='unbound_cache',
+        group=RulespecGroupCheckParametersApplications,
+        match_type='dict',
+        parameter_valuespec=_parameter_valuespec_unbound_cache,
+        title=lambda: _('Unbound Cache'),
+    )
+)
diff --git a/source/gui/wato/check_parameters/unbound_replies.py b/source/gui/wato/check_parameters/unbound_replies.py
new file mode 100644
index 0000000..f4bad54
--- /dev/null
+++ b/source/gui/wato/check_parameters/unbound_replies.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2022, Jan-Philipp Litza (PLUTEX) <jpl@plutex.de>.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+# modifications by thl-cmk[at]outlook[dot]com
+# 2024-04-21: fixed missing FixedValue in import
+#             changed Alternative titles to "Upper levels in qps"/"Upper levels in %" to better differentiate between them
+#             added explicit unit "%2
+#             added ruleset for bakery
+#             renamed to unbound.py (from unbound_parameters.py)
+#             moved to ~/local/lib/check_mk/gui/plugins/wato/check_parameters (from local/share/check_mk/web/plugins/wato)
+# 2024-05-14: separated WATO for bakery and check in two files
+# 2024-05-22: added ruleset for unwanted replies
+
+from cmk.gui.i18n import _
+from cmk.gui.plugins.wato.utils import (
+    CheckParameterRulespecWithoutItem,
+    RulespecGroupCheckParametersApplications,
+    rulespec_registry,
+)
+
+from cmk.gui.valuespec import (
+    Dictionary,
+    Float,
+    Tuple,
+)
+
+
+def _parameter_valuespec_unbound_replies():
+    return Dictionary(
+        title=_('Unbound: Replies'),
+        elements=[
+            ('unwanted_replies',
+             Tuple(
+                 title='Levels on unwanted replies per second',
+                 elements=[
+                     Float(title='warn', ),
+                     Float(title='crit', ),
+                 ])),
+        ])
+
+
+rulespec_registry.register(
+    CheckParameterRulespecWithoutItem(
+        check_group_name='unbound_replies',
+        group=RulespecGroupCheckParametersApplications,
+        match_type='dict',
+        parameter_valuespec=_parameter_valuespec_unbound_replies,
+        title=lambda: _('Unbound Replies'),
+    )
+)
diff --git a/source/gui/wato/check_parameters/unbound_status.py b/source/gui/wato/check_parameters/unbound_status.py
new file mode 100644
index 0000000..a58edde
--- /dev/null
+++ b/source/gui/wato/check_parameters/unbound_status.py
@@ -0,0 +1,41 @@
+# License: GNU General Public License v2
+#
+# Author: thl-cmk[at]outlook[dot]com
+# URL   : https://thl-cmk.hopto.org
+# Date  : 2024-09-19
+# File  : unbound_status.py
+
+from cmk.gui.i18n import _
+from cmk.gui.plugins.wato.utils import (
+    CheckParameterRulespecWithoutItem,
+    RulespecGroupCheckParametersApplications,
+    rulespec_registry,
+)
+
+from cmk.gui.valuespec import (
+    Dictionary,
+    MonitoringState,
+)
+
+
+def _parameter_valuespec_unbound_status():
+    return Dictionary(
+        title=_('Unbound: status'),
+        elements=[
+            ('status_not_running',
+             MonitoringState(
+                 title='Monitoring state if unbound is not running',
+                 default_value=2,
+             )),
+        ])
+
+
+rulespec_registry.register(
+    CheckParameterRulespecWithoutItem(
+        check_group_name='unbound_status',
+        group=RulespecGroupCheckParametersApplications,
+        match_type='dict',
+        parameter_valuespec=_parameter_valuespec_unbound_status,
+        title=lambda: _('Unbound status'),
+    )
+)
diff --git a/source/gui/wato/check_parameters/unbound_type_stats.py b/source/gui/wato/check_parameters/unbound_type_stats.py
new file mode 100644
index 0000000..2e34f0f
--- /dev/null
+++ b/source/gui/wato/check_parameters/unbound_type_stats.py
@@ -0,0 +1,115 @@
+#!/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-08-02
+# File  : unbound_type_stats.py
+
+# based on the work of ruettimann[at]init7[dot]net
+
+
+from collections.abc import Sequence
+from typing import Final
+
+from cmk.gui.i18n import _
+from cmk.gui.plugins.wato.utils import (
+    CheckParameterRulespecWithoutItem,
+    RulespecGroupCheckParametersApplications,
+    rulespec_registry,
+)
+
+from cmk.gui.valuespec import (
+    Alternative,
+    Dictionary,
+    FixedValue,
+    Float,
+    Percentage,
+    Tuple,
+)
+
+# https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml
+DNS_TYPES: Final[Sequence[str]] = [
+    'A',
+    'PTR',
+    'AAAA',
+    'ANY',
+    'CNAME',
+    'DNSKEY',
+    'DS',
+    'HINFO',
+    'HTTPS',
+    'MX',
+    'NAPTR',
+    'NS',
+    'RRSIG',
+    'SOA',
+    'SRV',
+    'SVCB',
+    'TXT',
+    # 'NULL',
+    # 'TYPE0',
+    # 'WKS',
+    'unknown',
+]
+
+DNS_TYPES_CHOICES: Final[Sequence[str]] = [(answer, answer) for answer in DNS_TYPES]
+
+_answer_levels = [
+    (f'levels_upper_{dns_type}',
+     Alternative(
+         title=f'Upper levels for {dns_type} answers',
+         show_alternative_title=True,
+         elements=[
+             Tuple(
+                 elements=[
+                     Float(title=_('Warning at'), unit=_('qps')),
+                     Float(title=_('Critical at'), unit=_('qps')),
+                 ],
+                 title=_('Upper levels in qps'),
+                 orientation='horizontal',
+             ),
+             Tuple(
+                 elements=[
+                     Percentage(title=_('Warning at'), unit=_('%')),
+                     Percentage(title=_('Critical at'), unit=_('%')),
+                     FixedValue(value='%', totext=''),  # needed to decide between both variants
+                 ],
+                 title=_('Upper levels in %'),
+                 orientation='horizontal',
+             ),
+         ]))
+    for dns_type in DNS_TYPES
+]
+
+_elements = _answer_levels
+# _elements.append(
+#     ('other_answer_types',
+#      DualListChoice(
+#          title=_('Other answer types'),
+#          help=_('Selected answer types will be summarized under "other"'),
+#          choices=DNS_TYPES_CHOICES,
+#          rows=len(DNS_TYPES_CHOICES),
+#          allow_empty=False,
+#      ))
+# )
+
+
+def _parameter_valuespec_unbound_type_stats():
+    return Dictionary(
+        title=_('Unbound type stats'),
+        elements=_elements,
+    )
+
+
+rulespec_registry.register(
+    CheckParameterRulespecWithoutItem(
+        check_group_name='unbound_type_stats',
+        group=RulespecGroupCheckParametersApplications,
+        match_type='dict',
+        parameter_valuespec=_parameter_valuespec_unbound_type_stats,
+        title=lambda: _('Unbound Type Stats'),
+    )
+)
diff --git a/source/packages/unbound b/source/packages/unbound
index f04deac..646b36a 100644
--- a/source/packages/unbound
+++ b/source/packages/unbound
@@ -11,17 +11,33 @@
                 'This plugin is based on the work of Jan-Philipp Litza '
                 '(PLUTEX) jpl[at]plutex[dor]de.\n'
                 'See:  https://exchange.checkmk.com/p/unbound for more '
-                'details.\n',
+                'details.\n'
+                '\n'
+                'ThX to ruettimann[at]init7[dot]net for the unbound_type_stats '
+                'check\n',
  'download_url': 'https://thl-cmk.hopto.org',
- 'files': {'agent_based': ['unbound.py', 'unbound_status.py'],
+ 'files': {'agent_based': ['unbound_status.py',
+                           'unbound.py',
+                           'unbound_answers.py',
+                           'unbound_cache.py',
+                           'unbound_unwanted_replies.py',
+                           'utils/unbound.py',
+                           'unbound_type_stats.py'],
            'agents': ['plugins/unbound'],
-           'gui': ['metrics/unbound.py',
-                   'wato/check_parameters/unbound.py',
-                   'wato/check_parameters/unbound_bakery.py'],
+           'gui': ['wato/check_parameters/unbound_bakery.py',
+                   'wato/check_parameters/unbound_answers.py',
+                   'wato/check_parameters/unbound_cache.py',
+                   'wato/check_parameters/unbound_replies.py',
+                   'metrics/unbound_answers.py',
+                   'metrics/unbound_cache.py',
+                   'metrics/unbound_unwanted_replies.py',
+                   'metrics/unbound_type_stats.py',
+                   'wato/check_parameters/unbound_type_stats.py',
+                   'wato/check_parameters/unbound_status.py'],
            'lib': ['python3/cmk/base/cee/plugins/bakery/unbound.py']},
  'name': 'unbound',
  'title': 'Unbound',
- 'version': '1.2.6-20240527',
+ 'version': '1.3.3-20240927',
  'version.min_required': '2.2.0b1',
  'version.packaged': 'cmk-mkp-tool 0.2.0',
  'version.usable_until': '2.4.0b1'}
-- 
GitLab