diff --git a/active_check_traceroute.mkp b/active_check_traceroute.mkp
new file mode 100644
index 0000000000000000000000000000000000000000..1fa711dfb8738d7caf5ba8e42dfd15a0da11910e
Binary files /dev/null and b/active_check_traceroute.mkp differ
diff --git a/checks/check_traceroute b/checks/check_traceroute
new file mode 100644
index 0000000000000000000000000000000000000000..a0eec4bb41bdb64c7ef14129fe3acbb59f3cbf73
--- /dev/null
+++ b/checks/check_traceroute
@@ -0,0 +1,69 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# Copyright (C) 2019 tribe29 GmbH - License: GNU General Public License v2
+# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
+# conditions defined in the file COPYING, which is part of this source code package.
+
+
+def check_traceroute_arguments(params):
+    args = []
+
+    if not params['dns']:
+        args.append('-n')
+    method = params['method']
+    if method == 'icmp':
+        args.append('-I')
+    elif method == 'tcp':
+        args.append('-T')
+    # else: None -> default method
+
+    for router, state in params['routers']:
+        args += ['-%s' % state, router]
+
+    family = params.get('address_family')
+    if family is None:
+        family = 'ipv6' if is_ipv6_primary(host_name()) else 'ipv4'
+
+    if family == 'ipv6':
+        args.append('-6')
+    else:
+        args.append('-4')
+
+    # additional options
+    if 'port' in params:
+        args.append(f'-p {params["port"]}')
+
+    if 'queries' in params:
+        args.append(f'-q {params["queries"]}')
+
+    if 'max_ttl' in params:
+        args.append(f'-m {params["max_ttl"]}')
+
+    # needs root
+    if 'source_interface' in params:
+        args.append(f'-i {params["source_interface"]}')
+    # needs root
+    if 'source_address' in params:
+        args.append(f'-s {params["source_address"]}')
+
+    if 'destination_address' in params:
+        args.append(params['destination_address'])
+    else:
+        args.append('$HOSTADDRESS$')
+
+    return args
+
+
+def _check_description(params):
+    if 'description' in params:
+        return f'Routing {params["description"]}'
+
+    return 'Routing'
+
+
+active_check_info['traceroute'] = {
+    'command_line': 'check_traceroute $ARG1$',
+    'argument_function': check_traceroute_arguments,
+    'service_description': _check_description,
+    'has_perfdata': True,
+}
diff --git a/lib/nagios/plugins/check_traceroute b/lib/nagios/plugins/check_traceroute
new file mode 100755
index 0000000000000000000000000000000000000000..dfa229014455cd29ba7a1d06fdf646c2b42bc4e7
--- /dev/null
+++ b/lib/nagios/plugins/check_traceroute
@@ -0,0 +1,297 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# Copyright (C) 2019 tribe29 GmbH - License: GNU General Public License v2
+# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
+# conditions defined in the file COPYING, which is part of this source code package.
+
+# This check does a traceroute to the specified target host
+# (usually $HOSTADDRESS$ itself) and checks which route(s) are
+# being taken. That way you can check if your preferred or
+# some alternative route in in place.
+# traceroute is expected to be in the search path and installed
+# with SUID root bit.
+
+# Example output from traceroute -n
+# traceroute to www.google.de (173.194.44.55), 30 hops max, 60 byte packets
+#  1  10.10.11.4  0.419 ms  0.444 ms  0.518 ms
+#  2  33.117.16.28  14.359 ms  14.371 ms  14.434 ms
+#  3  112.18.7.119  14.750 ms  14.765 ms  19.530 ms
+#  4  184.50.190.61  17.844 ms  17.865 ms  17.862 ms
+#  5  67.249.94.88  24.285 ms  78.527 ms  26.834 ms
+#  6  209.85.240.99  27.910 ms  27.420 ms  27.442 ms
+#  7  173.194.44.55  26.583 ms  20.410 ms  23.142 ms
+
+# Output without -n option:
+# traceroute to www.google.de (173.194.44.56), 30 hops max, 60 byte packets
+#  1  fritz.box (10.10.11.4)  0.570 ms  0.606 ms  0.677 ms
+#  2  foo-bar.x-online.net (33.117.16.28)  14.566 ms  14.580 ms  14.658 ms
+#  3  xu-2-3-0.rt-inxs-1.x-online.net (112.13.6.109)  18.214 ms  18.228 ms  18.221 ms
+#  4  * * *
+#  5  66.249.94.88 (66.249.94.88)  24.481 ms  24.498 ms  24.271 ms
+#  6  209.85.240.99 (209.85.240.99)  27.628 ms  21.605 ms  21.943 ms
+#  7  muc03s08-in-f24.1e100.net (173.194.44.56)  21.277 ms  22.236 ms  22.192 ms
+
+# Example output for IPv6
+# traceroute to ipv6.google.com (2404:6800:4004:80e::200e), 30 hops max, 80 byte packets
+#  1  2001:2e8:665:0:2:2:0:1 (2001:2e8:665:0:2:2:0:1)  0.082 ms  0.046 ms  0.044 ms
+#  2  2001:2e8:22:204::2 (2001:2e8:22:204::2)  0.893 ms  0.881 ms  0.961 ms
+#  3  * 2001:4860:0:1::1abd (2001:4860:0:1::1abd)  225.189 ms *
+#  4  2001:4860:0:1003::1 (2001:4860:0:1003::1)  3.052 ms  2.820 ms 2001:4860:0:1002::1 (2001:4860:0:1002::1)  1.501 ms
+#  5  nrt13s48-in-x0e.1e100.net (2404:6800:4004:80e::200e)  1.910 ms  1.828 ms  1.753 ms
+
+# It is also possible that for one hop several different answers appear:
+# 11 xe-0-0-1-0.co2-96c-1b.ntwk.msn.net (204.152.141.11)  174.185 ms xe-10-0-2-0.co1-96c-1a.ntwk.msn.net (207.46.40.94)  174.279 ms xe-0-0-1-0.co2-96c-1b.ntwk.msn.net (204.152.141.11)  174.444 ms
+
+# if DNS fails then it looks like this:
+#  5  66.249.94.88 (66.249.94.88)  24.481 ms  24.498 ms  24.271 ms
+#  6  209.85.240.99 (209.85.240.99)  27.628 ms  21.605 ms  21.943 ms
+
+import ast
+import getopt
+import ipaddress
+import os
+import subprocess
+import sys
+
+
+class MissingValueError(Exception):
+    pass
+
+
+class ProtocolVersionError(Exception):
+    pass
+
+
+class ExecutionError(Exception):
+    pass
+
+
+def parse_exception(exc):
+    exc = str(exc)
+    if exc[0] == "{":
+        exc = "%d - %s" % list(ast.literal_eval(exc).values())[0]
+    return str(exc)
+
+
+def output_check_result(s, perfdata):
+    if perfdata:
+        perfdata_output_entries = ["%s=%s" % (p[0], ";".join(map(str, p[1:]))) for p in perfdata]
+        s += " | %s" % " ".join(perfdata_output_entries)
+    sys.stdout.write("%s\n" % s)
+
+
+def option_to_state(c):
+    return {"w": 1, "c": 2}[c.lower()]
+
+
+def _execute_traceroute(target, nodns, method, address_family, queries, max_ttl, port, source_addr, source_int):
+    cmd = ["traceroute"]
+    if nodns:
+        cmd.append("-n")
+    if method:
+        cmd.append(method)
+    if address_family:
+        cmd.append(address_family)
+    if port and method != "-I":
+        cmd.append(f"-p {port}")
+    if queries:
+        cmd.append(f"-q {queries}")
+    if max_ttl:
+        cmd.append(f"-m {max_ttl}")
+    if source_int:
+        cmd.append(f"-i {source_int}")
+
+    if source_addr:
+        cmd.append(f"-s {source_addr}")
+
+    cmd.append(target)
+
+    if (source_int is not None) or (source_addr is not None):
+        cmd = ' '.join(cmd)
+        shell = True
+    else:
+        shell = False
+
+    p = subprocess.Popen(args=cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf8", shell=shell)
+    sto, ste = p.communicate()
+    if p.returncode:
+        raise ExecutionError("UNKNOWN - " + ste.replace("\n", " "))
+    return sto
+
+
+def check_traceroute(lines, routes):
+    # find all visited routers
+    routers = set([])
+    hops = len(lines[1:])
+    for line in lines[1:]:
+        parts = line.strip().split()
+        for part in parts:
+            try:
+                part = part.lstrip("(").rstrip(",").rstrip(")")
+                ipaddress.ip_interface(part)
+                routers.add(part)
+            except ValueError:
+                pass
+
+    state = 0
+    bad_routers = []
+    missing_routers = []
+    for option, route in routes:
+        s = option_to_state(option)
+        if option.islower() and route in routers:
+            state = max(state, s)
+            bad_routers.append("%s(%s)" % (route, "!" * s))
+        elif option.isupper() and route not in routers:
+            state = max(state, s)
+            missing_routers.append("%s(%s)" % (route, "!" * s))
+
+    info_text = "%d hops, missing routers: %s, bad routers: %s" % (
+        hops,
+        missing_routers and ", ".join(missing_routers) or "none",
+        bad_routers and ", ".join(bad_routers) or "none",
+    )
+    perfdata = [("hops", hops)]
+    return state, info_text, perfdata
+
+
+def validate_ip_version(address_arg, ip_version_arg):
+    # ipv6 address can have an appended interface index/name: 'fe80::%{interface}'
+    try:
+        ip_address_version = ipaddress.ip_interface(address_arg.split("%")[0]).ip.version
+    except ValueError:
+        # May be a host or DNS name, don't execute the validation in this case.
+        # check_traceroute will perform the name resolution for us.
+        return
+
+    if not ip_address_version == ip_version_arg:
+        raise ProtocolVersionError(
+            'IP protocol version "%s" not the same as the IP address version "%s".'
+            % (ip_version_arg, ip_address_version)
+        )
+
+
+def usage():
+    sys.stdout.write(
+        """check_traceroute -{c|w|C|W} ROUTE  [-{o|c|w|O|C|W} ROUTE...] TARGET
+
+Check by which routes TARGET is being reached. Each possible route is being
+prefixed with a state option:
+
+ -w Make outcome WARN if that route is present
+ -W Make outcome WARN if that route is missing
+ -c Make outcome CRIT if that route is present
+ -C Make outcome CRIT if that route is missing
+
+Other options:
+
+ -h, --help     show this help and exit
+ --debug        show Python exceptions verbosely
+ -n             disable reverse DNS lookups
+ -I             Use ICMP ECHO for probes
+ -T             Use TCP SYN for probes
+ -4             Use IPv4
+ -6             Use IPv6
+ -p port        Set the destination port to use
+ -m max_ttl     Set the max number of hops (max TTL to be reached). Default is 30
+ -s src_addr    Use source address
+ -i device      Specify a network interface to operate with
+ -q nqueries    Set the number of probes per each hop. Default is 3
+
+"""
+    )
+
+
+def main(args=None):
+    if args is None:
+        args = sys.argv[1:]
+
+    os.unsetenv("LANG")
+
+    opt_verbose = 0
+    opt_debug = False
+    opt_nodns = False
+    opt_method = None
+    opt_address_family = None
+    opt_port = None
+    opt_source_int = None
+    opt_source_addr = None
+    opt_queries = None
+    opt_max_ttl = None
+
+    short_options = "hw:W:c:C:i:s:p:q:m:nTI46"
+    long_options = [
+        "verbose",
+        "help",
+        "debug",
+    ]
+
+    route_params = []
+
+    try:
+        opts, args = getopt.getopt(args, short_options, long_options)
+
+        if len(args) < 1:
+            usage()
+            raise MissingValueError("Please specify the target destination.")
+
+        target_address = args[0]
+
+        # first parse modifers
+        for o, a in opts:
+            if o in ["-v", "--verbose"]:
+                opt_verbose += 1
+            elif o in ["-d", "--debug"]:
+                opt_debug = True
+            elif o in ["-w", "-W", "-c", "-C"]:
+                route_params.append((o[1], a))
+            elif o == "-n":
+                opt_nodns = True
+            elif o in ["-T", "-I"]:
+                opt_method = o
+            elif o in ["-4", "-6"]:
+                if opt_address_family:
+                    raise ProtocolVersionError("Cannot use both IPv4 and IPv6")
+                validate_ip_version(target_address, int(o.lstrip("-")))
+                opt_address_family = o
+            elif o in ["-s"]:
+                opt_source_addr = a
+            elif o in ["-i"]:
+                opt_source_int = a
+            elif o in ["-p"]:
+                opt_port = a
+            elif o in ["-q"]:
+                opt_queries = a
+            elif o in ["-m"]:
+                opt_max_ttl = a
+
+        # now handle action options
+        for o, a in opts:
+            if o in ["-h", "--help"]:
+                usage()
+                sys.exit(0)
+
+        sto = _execute_traceroute(target_address, opt_nodns, opt_method, opt_address_family, opt_queries,
+                                  opt_max_ttl, opt_port, opt_source_addr, opt_source_int)
+        status, output, perfdata = check_traceroute(sto.split("\n"), route_params)
+        info_text = output.strip() + "\n%s" % sto
+        return status, info_text, perfdata
+
+    except ExecutionError as e:
+        return 3, str(e), None
+
+    except MissingValueError as e:
+        return 3, str(e), None
+
+    except ProtocolVersionError as e:
+        return 3, str(e), None
+
+    except Exception as e:
+        if opt_debug:
+            raise
+        return 2, "Unhandled exception: %s" % parse_exception(e), None
+
+
+if __name__ == "__main__":
+    exitcode, info, perf = main()
+    output_check_result(info, perf)
+    sys.exit(exitcode)
diff --git a/packages/active_check_traceroute b/packages/active_check_traceroute
new file mode 100644
index 0000000000000000000000000000000000000000..9d4a911cc4db8e1cfc323da20a529d914e981ac8
--- /dev/null
+++ b/packages/active_check_traceroute
@@ -0,0 +1,37 @@
+{'author': 'Th.L. (thl-cmk[at]outlook[dot]com)',
+ 'description': 'extended traceroute check plugin\n'
+                '\n'
+                'adds the following options:\n'
+                '- service description suffix\n'
+                '- alternate destination address\n'
+                '- destination port for UDP/TCP path trace\n'
+                '- max hops\n'
+                '- queries per hop\n'
+                '- source address \n'
+                '- source interface (needs root permissions).\n'
+                '\n'
+                'Source address and source interface uses the "shell=True" '
+                'option in in\n'
+                'the "subprocess.Popen" command. This is highly insecure, so '
+                'be careful.\n'
+                '\n'
+                'Note: the original option TCP path trace also needs root '
+                'permissions.\n'
+                '\n'
+                'To give "traceroute" root permissions you need to set the '
+                'SUID bit for\n'
+                'your traceroute program, like "sudo chmod u+s '
+                '/usr/bin/traceroute.db" \n'
+                'on Ubuntu 20.04.3 LTS.\n',
+ 'download_url': 'https://thl-cmk.hopto.org',
+ 'files': {'checks': ['check_traceroute'],
+           'lib': ['nagios/plugins/check_traceroute'],
+           'web': ['plugins/wato/active_checks_routing.py',
+                   'plugins/metrics/traceroute.py']},
+ 'name': 'active_check_traceroute',
+ 'num_files': 4,
+ 'title': 'Active Check Traceroute',
+ 'version': '20212104.v.0.0.1a',
+ 'version.min_required': '2.0.0',
+ 'version.packaged': '2021.09.20',
+ 'version.usable_until': None}
\ No newline at end of file
diff --git a/web/plugins/metrics/traceroute.py b/web/plugins/metrics/traceroute.py
new file mode 100644
index 0000000000000000000000000000000000000000..9ee094f9109284f4267b8521ba36deaead3456d8
--- /dev/null
+++ b/web/plugins/metrics/traceroute.py
@@ -0,0 +1,32 @@
+#!/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  : 2021-12-04
+#
+#
+#
+
+from cmk.gui.i18n import _
+
+from cmk.gui.plugins.metrics import (
+    metric_info,
+    graph_info,
+    perfometer_info
+
+)
+
+metric_info['hops'] = {
+    'title': _('Number of hops'),
+    'unit': 'count',
+    'color': '41/a',
+}
+
+perfometer_info.append({
+    'type': 'linear',
+    'segments': ['hops'],
+    'total': 30,
+})
diff --git a/web/plugins/wato/active_checks_routing.py b/web/plugins/wato/active_checks_routing.py
new file mode 100644
index 0000000000000000000000000000000000000000..474e425250196def5f0ba3ab11432faf62982ad5
--- /dev/null
+++ b/web/plugins/wato/active_checks_routing.py
@@ -0,0 +1,179 @@
+#!/usr/bin/env python3
+# -*- encoding: utf-8; py-indent-offset: 4 -*-
+
+#
+# (c) 2013 Heinlein Support GmbH
+#          Robert Sander <r.sander@heinlein-support.de>
+#
+
+# This 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 in version 2.  This file is distributed
+# in the hope that it will be useful, but WITHOUT ANY WARRANTY;  with-
+# out even the implied warranty of  MERCHANTABILITY  or  FITNESS FOR A
+# PARTICULAR PURPOSE. See the  GNU General Public License for more de-
+# ails.  You should have  received  a copy of the  GNU  General Public
+# License along with GNU Make; see the file  COPYING.  If  not,  write
+# to the Free Software Foundation, Inc., 51 Franklin St,  Fifth Floor,
+# Boston, MA 02110-1301 USA.
+
+from cmk.gui.i18n import _
+from cmk.gui.valuespec import (
+    Dictionary,
+    ListOf,
+    Tuple,
+    Transform,
+    Checkbox,
+    DropdownChoice,
+    TextInput,
+    Integer,
+    TextAscii,
+)
+
+from cmk.gui.plugins.wato import (
+    rulespec_registry,
+    HostRulespec,
+)
+
+from cmk.gui.plugins.wato.active_checks import (
+    RulespecGroupActiveChecks
+)
+
+
+def _ip_address_family_element():
+    return (
+        'address_family',
+        DropdownChoice(
+            title=_('IP address family'),
+            choices=[
+                (None, _('Primary address family')),
+                ('ipv4', _('Enforce IPv4')),
+                ('ipv6', _('Enforce IPv6')),
+            ],
+            default_value=None,
+        ),
+    )
+
+
+def _transform_add_address_family(v):
+    v.setdefault('address_family', None)
+    return v
+
+
+def _valuespec_active_checks_traceroute():
+    return Transform(
+        Dictionary(
+            title=_('Check current routing'),
+            help=_(
+                'This active check uses <tt>traceroute</tt> in order to determine the current '
+                'routing from the monitoring host to the target host. You can specify any number '
+                'of missing or expected routes in order to detect e.g. an (unintended) failover '
+                'to a secondary route.'
+            ),
+            elements=[
+                ('description',
+                 TextAscii(
+                     title=_('Service Description suffix'),
+                     help=_('Must be unique for every host. The service description starts always with \"Routing\".'),
+                     size=30,
+                 )),
+                ('dns',
+                 Checkbox(
+                     title=_('Name resolution'),
+                     label=_('Use DNS to convert IP addresses into hostnames'),
+                     help=_(
+                         'If you use this option, then <tt>traceroute</tt> is <b>not</b> being '
+                         'called with the option <tt>-n</tt>. That means that all IP addresses '
+                         'are tried to be converted into names. This usually adds additional '
+                         'execution time. Also DNS resolution might fail for some addresses.'
+                     ),
+                 ),),
+                _ip_address_family_element(),
+                ('routers',
+                 ListOf(
+                     Tuple(
+                         elements=[
+                             TextInput(
+                                 title=_('Router (FQDN, IP-Address)'),
+                                 allow_empty=False,
+                             ),
+                             DropdownChoice(
+                                 title=_('How'),
+                                 choices=[
+                                     ('W', _('WARN - if this router is not being used')),
+                                     ('C', _('CRIT - if this router is not being used')),
+                                     ('w', _('WARN - if this router is being used')),
+                                     ('c', _('CRIT - if this router is being used')),
+                                 ],
+                             ),
+                         ]
+                     ),
+                     title=_('Router that must or must not be used'),
+                     add_label=_('Add Condition'),
+                 ),),
+                ('method',
+                 DropdownChoice(
+                     title=_('Method of probing'),
+                     choices=[
+                         (None, _('UDP (default behaviour of traceroute)')),
+                         ('icmp', _('ICMP Echo Request')),
+                         ('tcp', _('TCP SYN, needs root permissions')),
+                     ],
+                     default_value='icmp',
+                 ),),
+                ('port',
+                 Integer(
+                     title=_('Port'),
+                     help=_('Set the destination port to use. It is either initial udp port value for \"default\" '
+                            'method (incremented by each probe, default is 33434), or initial seq for \"icmp\" '
+                            '(incremented as well, default from 1), or some constant destination port for other '
+                            'methods (with default of 80 for \"tcp\", 53 for \"udp\", etc.)'),
+                     maxvalue=65535,
+                 ),),
+                ('destination_address',
+                 TextAscii(
+                     title=_('Alternate Destination'),
+                     help=_('Path trace to alternate destination instead of \"HOSTADDRESS\".'),
+                 ),),
+                ('queries',
+                 Integer(
+                     title=_('Number of queries'),
+                     help=_('Set the number of probes per each hop. Default is 3.'),
+                     default_value=3,
+                     minvalue=1,
+                     maxvalue=10,
+                 ),),
+                ('max_ttl',
+                 Integer(
+                     title=_('Max hops'),
+                     help=_('Set the max number of hops (max TTL to be reached). Default is 30'),
+                     default_value=30,
+                     minvalue=1,
+                     maxvalue=255,
+                 ),),
+                ('source_interface',
+                 TextAscii(
+                     title=_('Source interface'),
+                     help=_('Specify a network interface to operate with. Needs root permissions.'),
+                 ),),
+                ('source_address',
+                 TextAscii(
+                     title=_('Source address'),
+                     help=_('Use source source address for outgoing packets'),
+                 ),),
+            ],
+            optional_keys=['description', 'max_ttl', 'queries', 'destination_address', 'source_address',
+                           'source_interface', 'port'],
+        ),
+        forth=_transform_add_address_family,
+    )
+
+
+rulespec_registry.register(
+    HostRulespec(
+        group=RulespecGroupActiveChecks,
+        match_type='all',
+        name='active_checks:traceroute',
+        valuespec=_valuespec_active_checks_traceroute,
+    )
+)