#!/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.

if_inventory_porttypes = [ "6", "ethernetCsmacd", "117", "gigabitEthernet" ]
if_inventory_uses_description = 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_if(checkname, info):
    inventory = []
    for ifIndex, ifDescr, ifType, ifSpeed, ifOperStatus, ifInOctets, ifInUcastPkts, ifInNUcastPkts, ifInDiscards, \
        ifInErrors, ifOutOctets, ifOutUcastPkts, ifOutNUcastPkts, ifOutDiscards, ifOutErrors, ifOutQLen in info:
        ifDescr = cleanup_if_strings(ifDescr)

        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
            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 if_statename(st):
    names = { '1': 'up',      '2': 'down',
              '3': 'testing', '4': 'unknown',
              '5': 'dormant', '6': 'notPresent',
              '7': 'lowerLayerDown' }
    return names.get(st, st)

def check_if(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, ifDescr, ifType, ifSpeed, ifOperStatus, ifInOctets, ifInUcastPkts, ifInNUcastPkts, ifInDiscards, \
        ifInErrors, ifOutOctets, ifOutUcastPkts, ifOutNUcastPkts, ifOutDiscards, ifOutErrors, ifOutQLen in info:
        ifDescr = cleanup_if_strings(ifDescr)
	
        if item == ifIndex 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 == ifDescr or ifDescr == ''): # description trvial
                infotext = ""
            elif item != ifDescr and ifDescr != '': # description useful
                infotext = "[%s] " % ifDescr
            else:
                infotext = "[%s] " % ifIndex

            operstatus = if_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)
            bandwidth = speed / 8.0 # in Bytes / sec
            if speed != targetspeed:
                infotext += "%s (wrong speed!)" % speed
                state = 1
            else:
                infotext += get_bytes_human_readable(speed, 1000)+'it/s'

            # 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",   ifInUcastPkts, None, None, None, None),
                ( "innucast",  ifInNUcastPkts, 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",  ifOutUcastPkts, None, None, None, None),
                ( "outnucast", ifOutNUcastPkts, 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 = []
                infotext += ', Counter wrapped - Skipping checkresult'
            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['if'] = (check_if, "Interface %s", 1,  inventory_if)
snmp_info['if'] = \
  ( ".1.3.6.1.2.1.2.2.1", [
  1, # ifIndex
  2, # ifDescr
  3, # ifType
  5, # ifSpeed
  8, # ifOperStatus
  10, # ifInOctets
  11, # ifInUcastPkts
  12, # ifInNUcastPkts
  13, # ifInDiscards
  14, # ifInErrors
  16, # ifOutOctets
  17, # ifOutUcastPkts
  18, # ifOutNUcastPkts
  19, # ifOutDiscards
  20, # ifOutErrors
  21, # ifOutQLen
  ] )

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

check_config_variables.append("nagios_illegal_chars")
