#!/usr/bin/python
# encoding: utf-8
# +------------------------------------------------------------------+
# |             ____ _               _        __  __ _  __           |
# |            / ___| |__   ___  ___| | __   |  \/  | |/ /           |
# |           | |   | '_ \ / _ \/ __| |/ /   | |\/| | ' /            |
# |           | |___| | | |  __/ (__|   <    | |  | | . \            |
# |            \____|_| |_|\___|\___|_|\_\___|_|  |_|_|\_\           |
# |                                                                  |
# | Copyright Mathias Kettner 2013             mk@mathias-kettner.de |
# +------------------------------------------------------------------+
#
# This file is part of Check_MK.
# The official homepage is at http://mathias-kettner.de/check_mk.
#
# check_mk 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.  check_mk 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.

# 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

# 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 os, sys, getopt, subprocess

def option_to_state(c):
    return { "w" : 1, "c" : 2 }[c.lower()]

def check_traceroute(target, routes, nodns, method):
    args = ["traceroute"]
    if nodns:
        args.append("-n")
    if method:
        args.append(method)
    args.append(target)
    p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False)
    sto, ste = p.communicate()
    if p.returncode:
        return 3, "UNKNOWN - " + ste.replace("\n", " "), ""

    # find all visited routers
    routers = set([])
    for line in sto.split("\n")[1:]:
        parts = line.strip().split()
        for part in parts:
            if part.count(".") == 3 or (
                not part[0].isdigit() and part not in [ "ms", "*" ]):
                part = part.lstrip("(").rstrip(",").rstrip(")")
                routers.add(part)

    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 = "%s - %d hops, missing routers: %s, bad routers: %s" % (
        { 0:"OK", 1:"WARN", 2:"CRIT" }[state],
        len(sto.split("\n")[1:]),
        missing_routers and ", ".join(missing_routers) or "none",
        bad_routers and ", ".join(bad_routers) or "none")

    return state, info, sto

def bail_out(reason):
    sys.stderr.write("FATAL ERROR: %s\n" % reason)
    sys.exit(3)

def usage():
    print """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

"""

os.unsetenv("LANG")

opt_verbose       = 0
opt_debug         = False
opt_nodns         = False
opt_method        = None

short_options = "hw:W:c:C:nTI"
long_options = [ "verbose", "help", "debug", ]

routes = []

try:
    opts, args = getopt.getopt(sys.argv[1:], short_options, long_options)

    # 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' ]:
            routes.append((o[1], a))
        elif o == '-n':
            opt_nodns = True
        elif o in [ '-T', '-I' ]:
            opt_method = o

    # now handle action options
    for o, a in opts:
        if o in [ '-h', '--help' ]:
            usage()
            sys.exit(0)

    if len(args) < 1:
        bail_out("Please specify the target destination.")

    target = args[0]

    status, output, long_output = check_traceroute(target, routes, opt_nodns, opt_method)
    sys.stdout.write(output.strip() + "\n" + long_output)
    sys.exit(status)


except Exception, e:
    if opt_debug:
        raise
    bail_out(e)

