#!/usr/bin/perl

# check_tcptraffic is a nagios Nagios plugin to monitor the amount of TCP traffic
#
# check_tcptraffic uses the /proc/net/dev Linux entry to compute the
# amount of transferred bytes from the last plugin execution (temporary
# data is stored in the /tmp/check_tcptraffic-iface file)
#
# See  the INSTALL file for installation instructions
#
# Copyright (c) 2007, ETH Zurich.
#
# This module is free software; you can redistribute it and/or modify it
# under the terms of GNU general public license (gpl) version 3.
# See the LICENSE file for details.
#
# RCS information
# enable substitution with:
#   $ svn propset svn:keywords "Id Revision HeadURL Source Date"
#
#   $Id$
#   $Revision$
#   $HeadURL$
#   $Date$

use strict;
use warnings;

use English qw(-no_match_vars);
use Getopt::Long;
use Nagios::Plugin::Threshold;
use Nagios::Plugin;
use Pod::Usage qw(pod2usage);

use version; our $VERSION = '2.0.0';

# IMPORTANT: Nagios plugins could be executed using embedded perl in this case
#            the main routine would be executed as a subroutine and all the
#            declared subroutines would therefore be inner subroutines
#            This will cause all the global lexical variables not to stay shared
#            in the subroutines!
#
# All variables are therefore declared as package variables...
#
use vars qw(
  $tmp
  $critical
  $debug
  $help
  $interface
  $plugin
  $reset
  $result
  $threshold
  $speed
  $status
  $status_msg
  $warning
);

##############################################################################
# subroutines

##############################################################################
# Usage     : whoami()
# Purpose   : retrieve the user runnging the process
# Returns   : username
# Arguments : n/a
# Throws    : n/a
# Comments  : n/a
# See also  : n/a
sub whoami {

    my $output;

    my $pid = open $output, q{-|}, 'whoami'
      or
      $plugin->nagios_exit( UNKNOWN, "Cannot determine the user: $OS_ERROR" );

    while (<$output>) {
        chomp;
        return $_;
    }

    $plugin->nagios_exit( UNKNOWN, 'Cannot determine the user' );

    return;
}

##############################################################################
# Usage     : write_timer($data_in, $data_out)
# Purpose   : writes the time and transmit data to the temporary file
# Returns   : n/a
# Arguments : n/a
# Throws    : n/a
# Comments  : n/a
# See also  : n/a
sub write_timer {

    my $in  = shift;
    my $out = shift;

    my $TMP;    # file handler

    open $TMP, q{>}, $tmp
      or $plugin->nagios_exit( UNKNOWN, "Cannot initialize timer: $OS_ERROR" );

    print {$TMP} time . " $in $out\n";

    close $TMP
      or $plugin->nagios_exit( UNKNOWN, "Cannot close timer: $OS_ERROR" );

    return;

}

##############################################################################
# Usage     : read_proc('eth0')
# Purpose   : reads information about an interface in the proc file system
# Returns   : an hash containing the interface info
# Arguments : iface : interface name
# Throws    : n/a
# Comments  : n/a
# See also  : n/a
sub read_proc {

    my $iface = shift;

    my %data;

    my $found = 0;
    my $in;
    my $out;
    my $time;

    my $IN;     # file descriptor
    my $TMP;    # file descriptor

    my $dev_file = '/proc/net/dev';

    open $IN, q{<}, $dev_file
      or $plugin->nagios_exit( UNKNOWN, "Cannot open $dev_file: $OS_ERROR" );

    while (<$IN>) {

        chomp;

        if (/:/mx) {

         # /proc/net/dev format
         #
         # bytes:      The total number of bytes of data transmitted or received
         #             by the interface.
         # packets:    The total number of packets of data transmitted or
         #             received by the interface.
         # errs:       The total number of transmit or receive errors detected
         #             by the device driver.
         # drop:       The total number of packets dropped by the device driver.
         # fifo        The number of FIFO buffer errors.
         # frame:      The number of packet framing errors.
         # compressed: The number of compressed packets transmitted or received
         #             by the device driver.
         # multicast:  The number of multicast frames transmitted or received by
         #             the device driver.

            my (
                $combined,      $packets_in,   $errs_in,
                $drop_in,       $fifo_in,      $frame_in,
                $compressed_in, $multicast_in, $bytes_out,
                $packets_out,   $errs_out,     $drop_out,
                $fifo_out,      $frame_out,    $compressed_out,
                $multicast_out
            ) = split;

            my ( $name, $bytes_in ) = split /:/mx, $combined;

            if ( $name eq $interface ) {

                $found = 1;

                $data{combined}       = $combined;
                $data{packets_in}     = $packets_in;
                $data{errs_in}        = $errs_in;
                $data{drop_in}        = $drop_in;
                $data{fifo_in}        = $fifo_in;
                $data{frame_in}       = $frame_in;
                $data{compressed_in}  = $compressed_in;
                $data{multicast_in}   = $multicast_in;
                $data{bytes_out}      = $bytes_out;
                $data{packets_out}    = $packets_out;
                $data{errs_out}       = $errs_out;
                $data{drop_out}       = $drop_out;
                $data{fifo_out}       = $fifo_out;
                $data{frame_out}      = $frame_out;
                $data{compressed_out} = $compressed_out;
                $data{multicast_out}  = $multicast_out;
                $data{bytes_in}       = $bytes_in;

                # get the time difference
                if ( $reset || !open $TMP, q{<}, $tmp ) {
                    write_timer( $bytes_in, $bytes_out );
                    $plugin->nagios_exit( UNKNOWN, 'Initializing timer' );
                }

                while (<$TMP>) {
                    chomp;
                    ( $time, $in, $out ) = split;
                    $data{diff} = time - $time;
                    $data{in}   = $in;
                    $data{out}  = $out;
                }

                close $TMP
                  or $plugin->nagios_exit( UNKNOWN,
                    "Cannot close $tmp: $OS_ERROR" );

                write_timer( $bytes_in, $bytes_out );

                last;

            }

        }

    }

    if ( !$found ) {
        $plugin->nagios_exit( UNKNOWN, "Interface $interface not found" );
    }

    close $IN
      or $plugin->nagios_exit( UNKNOWN, "Cannot close $dev_file: $OS_ERROR" );

    return %data;

}

##############################################################################
# main
#

################
# Initialization

$critical   = 0;
$help       = q{};
$interface  = q{};
$plugin     = Nagios::Plugin->new( shortname => 'TCPTRAFFIC' );
$reset      = q{};
$speed      = q{};
$status     = 0;
$status_msg = q{};
$warning    = 0;

########################
# Command line arguments

$result = GetOptions(
    'critical=i'  => \$critical,
    'debug'       => \$debug,
    'interface=s' => \$interface,
    'help'        => \$help,
    'reset'       => \$reset,
    'speed=i'     => \$speed,
    'warning=i'   => \$warning,
    'version' => sub { print "check_tcptraffic version $VERSION\n"; exit 3; }
);

if ( !$result ) {
    pod2usage();
}

if ($help) { pod2usage(); }
if ( $critical <= 0 ) { pod2usage( -message => 'Could not parse "critical"' ); }
if ( !$interface )   { pod2usage( -message => 'Could not parse "interface"' ); }
if ( !$speed )       { pod2usage( -message => 'Could not parse "speed"' ); }
if ( $warning <= 0 ) { pod2usage( -message => 'Could not parse "warning"' ); }
if ( $critical < $warning ) {
    pod2usage('"critical" has to be greater than "warning"');
}

# check the speed
my $max_check_time = int ( 32_768 / $speed ) ;    # in seconds

$threshold = Nagios::Plugin::Threshold->set_thresholds(
    warning  => $warning,
    critical => $critical,
);

$tmp = "/tmp/check_tcptraffic_status-$interface" . whoami();

########################
# Check the proc entry

my %data = read_proc($interface);

if ( $data{diff} > $max_check_time ) {

    if ($debug) {
        print
          "$data{diff} > $max_check_time: sleeping 1s to gather data again\n";
    }

    # time difference is > max_check_time
    # since the counter could overflow
    # we reeinitilize the timer and
    # we perform a 1s check
    write_timer( $data{bytes_in}, $data{bytes_out} );

    sleep 1;

    %data = read_proc($interface);

}

if ( $data{diff} == 0 ) {

    # round up
    $data{diff} = 1;
}

my $traffic_in  = abs( int( ( $data{bytes_in} - $data{in} ) / $data{diff} ) );
my $traffic_out = abs( int( ( $data{bytes_out} - $data{out} ) / $data{diff} ) );
my $traffic = $traffic_in + $traffic_out;

$plugin->add_perfdata(
    label     => 'TOTAL',
    value     => sprintf( '%.0f', $traffic ),
    uom       => q{},
    threshold => $threshold,
);

$plugin->add_perfdata(
    label     => 'IN',
    value     => sprintf( '%.0f', $traffic_in ),
    uom       => q{},
);

$plugin->add_perfdata(
    label     => 'OUT',
    value     => sprintf( '%.0f', $traffic_out ),
    uom       => q{},
);

$plugin->add_perfdata(
    label     => 'TIME',
    value     => sprintf( '%.0f', $data{diff}  ),
    uom       => q{},
);

$plugin->nagios_exit(
    $threshold->get_status($traffic),
    "$interface " . sprintf( '%.0f', $traffic ) . " bytes/s"
);

1;

__END__

=pod

=head1 NAME

C<check_tcptraffic> - A Nagios plugin to monitor the amount of TCP traffic


=head1 DESCRIPTION

check_tcptraffic is a nagios Nagios plugin to monitor the amount of TCP traffic

check_tcptraffic uses the /proc/net/dev Linux entry to compute the
amount of transferred bytes from the last plugin execution (temporary
data is stored in the /tmp/check_tcptraffic-iface file)

Since /proc/net/dev uses 32bit counters overflows are a problem
(especially on a fast interface)

 Speed           Maximum safe check period
 -----           -------------------------

 10Mbit/s        55'
 100Mbit/s       5'8"
 q1GBit/s         32s

=head1 VERSION

Version 2.0.0

=head1 SYNOPSIS

 check_tcptraffic --critical=crit --warning=warn --interface=iface --speed=speed
                  [--verbose] [--reset] [--help]

 Required arguments
  --critical,c  crit      critical
  --warning,w   warn      warning
  --interface,i iface     network interface
  --speeed,s              speed (in Mbit/s)

 Options
  --help,-h,-?            this help message
  --reset,r               initialize counter
  --version,V             print version and exit
  --verbose,v             verbose

=head1 REQUIRED ARGUMENTS

 --speed, --interface, --critical and --warning

=head1 OPTIONS

  --interface=name,-i name      network interface

  --critical=value,-c value     critical number of sectors/s

  --warning=value,-w value      number of sectors/s which generates a warning

  --help,-h,-?                  help message

  --reset,r                     initialize counter

  --speeed=value,-s value       speed (in Mbit/s)

  --version,V                   print version and exit

  --verbose,v                   verbose output

=head1 EXAMPLE

check_tcptraffic --warning=20971520 --critical=83886080 --interface=eth0

check_tcptraffic checks that the number of transferred bytes on eth0 will
stay below the specified limits

=head1 DIAGNOSTICS

You can specify multiple --verbose options to increase the program
verbosity.

=head1 EXIT STATUS

0 if OK, 1 in case of a warning, 2 in case of a critical status and 3
in case of an unknown problem

=head1 DEPENDENCIES

check_diskio depends on

=over 4

=item * English

=item * Getopt::Long

=item * Nagios::Plugin

=item * Nagios::Plugin::Threshold

=item * Pod::Usage

=item * version

=back

=head1 CONFIGURATION

=head1 INCOMPATIBILITIES

None reported.

=head1 SEE ALSO

Nagios documentation

=head1 BUGS AND LIMITATIONS

No bugs have been reported.

Please report any bugs or feature requests to matteo.corti@id.ethz.ch,
or through the web interface at
https://svn.id.ethz.ch/trac/bd_webhosting/newticket

=head1 AUTHOR

Matteo Corti <matteo.corti@id.ethz.ch>

=head1 LICENSE AND COPYRIGHT

Copyright (c) 2007, ETH Zurich.

This module is free software; you can redistribute it and/or modify it
under the terms of GNU general public license (gpl) version 3.
See the LICENSE file for details.

=head1 DISCLAIMER OF WARRANTY

BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT
WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER
PARTIES PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND,
EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR, OR CORRECTION.

IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE LIABLE
TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.

=head1 ACKNOWLEDGMENTS

