#!/usr/bin/python
# -*- encoding: utf-8; py-indent-offset: 4 -*-
# +------------------------------------------------------------------+
# |             ____ _               _        __  __ _  __           |
# |            / ___| |__   ___  ___| | __   |  \/  | |/ /           |
# |           | |   | '_ \ / _ \/ __| |/ /   | |\/| | ' /            |
# |           | |___| | | |  __/ (__|   <    | |  | | . \            |
# |            \____|_| |_|\___|\___|_|\_\___|_|  |_|_|\_\           |
# |                                                                  |
# | Copyright Mathias Kettner 2010             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.

# Share settings with if, so user needs to configure this
# only once. Important: The default settings must be the same
# as in 'if' since they overwrite each other.
if_inventory_porttypes = [ "6", "ethernetCsmacd", "117", "gigabitEthernet" ]
if_inventory_uses_description = False
if_inventory_uses_alias = False
# first two: Error percentages, second two: Bandwidth thresholds, allowed operstates
if_default_levels = (0.01, 0.1, None, None, ('1', 'up'))

# Remove 0 bytes from strings. They lead to problems e.g. here:
# On windows hosts the labels of network interfaces in oid
# iso.3.6.1.2.1.2.2.1.2.1 are given as hex strings with tailing
# 0 byte. When this string is part of the data which is sent to
# the nagios pipe all chars after the 0 byte are stripped of.
# Stupid fix: Remove all 0 bytes. Hope this causes no problems.
def cleanup_if_strings(s):
    if s and s != '':
        return "".join([ c for c in s if c not in nagios_illegal_chars+chr(0) ]).strip()
    else:
        return s

def inventory_if64(checkname, info):
    if len(info) == 0 or len(info[0]) != 19:
        return []
    inventory = []
    for ifIndex, ifAlias, ifType, ifSpeed, ifOperStatus, ifInOctets, inucast, inmcast, inbcast, ifInDiscards, \
        ifInErrors, ifOutOctets, outucast, outmcast, outbcast, ifOutDiscards, ifOutErrors, ifOutQLen, ifDescr in info:
        ifDescr = cleanup_if_strings(ifDescr)
        ifAlias = cleanup_if_strings(ifAlias)
        allowed_operstates = ['1','up']
        if len(if_default_levels) == 5:
            allowed_operstates = if_default_levels[4]

        if ifType in if_inventory_porttypes and ifOperStatus in allowed_operstates:
            if if_inventory_uses_description and ifDescr:
                item = ifDescr
            elif if_inventory_uses_alias and ifAlias:
                item = ifAlias
            else:
                item = ifIndex

            if ifSpeed != "":
                add_levels = ''
                if len(if_default_levels) >= 4:
                    add_levels += ', if_default_levels[2], if_default_levels[3]'
                if len(if_default_levels) == 5:
                    add_levels += ', if_default_levels[4]'
                inventory.append( (item, "(if_default_levels[0], if_default_levels[1], %d%s)" % (int(ifSpeed), add_levels)) )
    return inventory

def if64_statename(st):
    names = { '1': 'up',      '2': 'down',
              '3': 'testing', '4': 'unknown',
              '5': 'dormant', '6': 'notPresent',
              '7': 'lowerLayerDown' }
    return names.get(st, st)

def check_if64(item, params, info):

    allowed_operstates = ('1', 'up')
    bw_warn, bw_crit = None, None
    num_params = len(params)
    if num_params == 3:
        err_warn, err_crit, targetspeed = params
    elif num_params == 5:
        err_warn, err_crit, targetspeed, bw_warn, bw_crit = params
    elif num_params == 6:
        err_warn, err_crit, targetspeed, bw_warn, bw_crit, allowed_operstates = params

    for ifIndex, ifAlias, ifType, ifSpeed, ifOperStatus, ifInOctets, inucast, inmcast, inbcast, ifInDiscards, \
        ifInErrors, ifOutOctets, outucast, outmcast, outbcast, ifOutDiscards, ifOutErrors, ifOutQLen, ifDescr in info:
        ifDescr = cleanup_if_strings(ifDescr)
        ifAlias = cleanup_if_strings(ifAlias)

        if item == ifIndex or item == ifAlias or item == ifDescr:

            # Display port number or alias in infotext if that is not part
            # of the service description anyway
            if item == ifIndex and (item == ifAlias or ifAlias == '') and (item == ifDescr or ifDescr == ''): # description trvial
                infotext = ""
            elif item != ifAlias and ifAlias != '': # alias useful
                infotext = "[%s] " % ifAlias
            elif item != ifDescr and ifDescr != '': # description useful
                infotext = "[%s] " % ifDescr
            else:
                infotext = "[%s] " % ifIndex

            operstatus = if64_statename(str(ifOperStatus))
            if not operstatus in allowed_operstates:
                return (2, "CRIT - %soperstate: %s (CRIT)" % (infotext, operstatus))
	    infotext += "(%s) " % operstatus

            state = 0

            # Check speed
            speed = saveint(ifSpeed)
            infotext += get_nic_speed_human_readable(speed)
            bandwidth = speed / 8.0 # in Bytes / sec
            if speed != targetspeed:
                infotext += " (wrong speed!)"
                state = 1

            # Performance counters
            this_time = time.time()
            rates = []
            wrapped = False
            perfdata = []
            for name, counter, warn, crit, min, max in [
                ( "in",        ifInOctets, bw_warn, bw_crit, 0, bandwidth),
                ( "inucast",   inucast, None, None, None, None),
                ( "innucast",  saveint(inmcast) + saveint(inbcast), None, None, None, None),
                ( "indisc",    ifInDiscards, None, None, None, None),
                ( "inerr",     ifInErrors, err_warn, err_crit, None, None),

                ( "out",       ifOutOctets, bw_warn, bw_crit, 0, bandwidth),
                ( "outucast",  outucast, None, None, None, None),
                ( "outnucast", saveint(outmcast) + saveint(outbcast), None, None, None, None),
                ( "outdisc",   ifOutDiscards, None, None, None, None),
                ( "outerr",    ifOutErrors, err_warn, err_crit, None, None) ]:

                try:
                    timedif, rate = get_counter("if.%s.%s" % (name, item), this_time, saveint(counter))
                    rates.append(rate)
                    perfdata.append( (name, rate, warn, crit, min, max) )
                except MKCounterWrapped:
                    wrapped = True
                    # continue, other counters might wrap as well

            # if at least one counter wrapped, we do not handle the counters at all
            if wrapped:
                perfdata = []
            else:
                perfdata.append(("outqlen", saveint(ifOutQLen)))
                for what, errorrate, okrate, traffic in \
                   [ ("in",  rates[4], rates[1] + rates[2], rates[0]),
                     ("out", rates[9], rates[6] + rates[7], rates[5]) ]:
                    infotext += ", %s: %s/s" % (what, get_bytes_human_readable(traffic))

                    # Check bandwidth thresholds
                    if not bw_crit is None and traffic >= bw_crit:
                        state = 2
                        infotext += ' (CRIT) >=' + get_bytes_human_readable(bw_crit)
                    elif not bw_warn is None and traffic >= bw_warn:
                        state = 1
                        infotext += ' (WARN) >=' + get_bytes_human_readable(bw_warn)

                    pacrate = okrate + errorrate
                    if pacrate > 0.0: # any packets transmitted?
                        errperc = 100.0 * errorrate / (okrate + errorrate)

                        if errperc > 0:
                            infotext += ", %s-errors: %.2f%%" % (what, errperc)

                        if errperc >= err_crit:
                            if state < 2:
                                state = 2
                            infotext += "(CRIT) " + str(err_crit)
                        elif errperc >= err_warn:
                            if state < 1:
                                state = 1
                            infotext += "(WARN) " + str(err_warn)

            return (state, "%s - %s" % (nagios_state_names[state], infotext), perfdata)


    return (3, "UNKNOWN - no such interface")

check_info['if64'] = (check_if64, "Interface %s", 1,  inventory_if64)
snmp_info['if64'] = \
  ( ".1.3.6.1.2.1", [
    "2.2.1.1",     # ifIndex
    "31.1.1.1.18", # ifAlias
    "2.2.1.3",     # ifType
    "2.2.1.5",     # ifSpeed
    "2.2.1.8",     # ifOperStatus
    "31.1.1.1.6",  # ifHCInOctets
    "31.1.1.1.7",  # ifHCInUcastPkts
    "31.1.1.1.8",  # ifHCInMulticastPkts
    "31.1.1.1.9",  # ifHCInBroadcastPkts
    "2.2.1.13",    # ifInDiscards
    "2.2.1.14",    # ifInErrors
    "31.1.1.1.10", # ifHCOutOctets
    "31.1.1.1.11", # ifHCOutUcastPkts
    "31.1.1.1.12", # ifHCOutMulticastPkts
    "31.1.1.1.13", # ifHCOutBroadcastPkts
    "2.2.1.19",    # ifOutDiscards
    "2.2.1.20",    # ifOutErrors
    "2.2.1.21",    # ifOutQLen
    "2.2.1.2",     # ifDescr
  ] )

# check if number of network interfaces (IF-MIB::ifNumber.0) is at least 2
snmp_scan_functions['if64'] = \
        lambda oid: not not oid(".1.3.6.1.2.1.31.1.1.1.6.1")

check_config_variables.append("nagios_illegal_chars")
