#!/usr/bin/perl -w
#
# tor-bandwidth-usage - munin plugin to monitor Tor traffic
#
# To use this plugin you need the following:
#   o Enable accounting on torrc configuration file (even if you dont want to limit bandwidth usage,
#     just put a huge value for on AccountingMax)
#     example:
#       AccountingStart day 12:00
#       AccountingMax 100 GB
#   o Enable CookieAuthentication (CookieAuthentication 1 in torrc) or define a HashedControlPassword
#   o Add something like the following to /etc/munin/plugin-conf.d/munin-node:
#       [tor-bandwidth-usage]
#       user debian-tor
#       env.cookiefile /var/run/tor/control.authcookie
#
#
# tested with Tor releases: 0.2.1.28, 0.2.1.29, 0.2.2.35
#
# Author: tazoi <dev AT tazoi DOT it>, based on a plugin by Ævar Arnfjörð Bjarmason <avarab@gmail.com>
#
# Parameters understood (defined in file /etc/munin/plugin-conf.d/munin-node or in environment)
#       host       - Change which host to graph (default localhost)
#       port       - Change which port to connect to (default 9051)
#	password   - Plain-text control channel password (see torrc
#                    HashedControlPassword parameter)
#	cookiefile - Name of the file containing the control channel cookie
#                    (see torrc CookieAuthentication parameter)
#
# Using HashedControlPassword authentication has the problem that you
# must include the plain-text password in the munin config file. To
# have any effect, that file shouldn't be world-readable.
#
# If you're using CookieAuthentication, you should run this plugin as
# a user which has read access to the tor datafiles. Also note that
# bugs in versions upto and including 0.1.1.20 prevent
# CookieAuthentication from working.
#
# Usage: place in /etc/munin/plugins (or link it there using ln -s)
#
#%# family=contrib
#%# capabilities=autoconf

use strict;
use feature ':5.10';
use IO::Socket::INET;
use Munin::Plugin;

# Config
my $address = $ENV{host}  || "localhost";
my $port    = $ENV{port}  || 9051;

# Don't edit below this line

sub Authenticate
{
    my ($socket) = @_;
    my $authline = "AUTHENTICATE";
    if (defined($ENV{cookiefile})) {
        if (open(COOKIE, "<$ENV{cookiefile}")) {
            my $cookie;
            binmode COOKIE;
            $authline .= " ";
            while (read(COOKIE, $cookie, 32)) {
                foreach my $byte (unpack "C*", $cookie) {
                    $authline .= sprintf "%02x", $byte;
                }
            }
            close COOKIE;
        }
    } elsif (defined($ENV{password})) {
        $authline .= ' "' . $ENV{password} . '"';
    }
    say $socket "$authline";
    my $replyline = <$socket>;
    if (substr($replyline, 0, 1) != '2') {
        $replyline =~ s/\s*$//;
        return "Failed to authenticate: $replyline";
    }

    return;
}

if ($ARGV[0] and $ARGV[0] eq "autoconf") {
    # Try to connect to the daemon
    my $socket = IO::Socket::INET->new("$address:$port") or my $failed = 1;

    if ($failed) {
        say "no (failed to connect to $address port $port)";
        exit 1;
    }

    my $msg = Authenticate($socket);
    if (defined($msg)) {
        say $socket "QUIT";
        close($socket);
        say "no ($msg)";
        exit 1;
    }

    say $socket "QUIT";
    close($socket);
    say "yes";
    exit 0;
}

if ($ARGV[0] and $ARGV[0] eq "config") {
    say "graph_order down up";
    say "graph_title Tor traffic";
    say "graph_args --base 1000";
    say "graph_vlabel bits in (-) / out (+) per \${graph_period}";
    say "graph_category network";
    say "graph_info This graph shows the traffic through this Tor node.";
    say "down.label received";
    say "down.type DERIVE";
    say 'down.graph no';
    say "down.cdef down,8,*";
    say "down.min 0";
    say "up.label b/s";
    say "up.type DERIVE";
    say "up.negative down";
    say "up.cdef up,8,*";
    say "up.min 0";

    exit 0;
}

my $socket = IO::Socket::INET->new("$address:$port")
    or die("Couldn't connect to $address port $port: $!");

my $msg = Authenticate($socket);
if (defined($msg)) {
    say $socket "QUIT";
    close($socket);
    die "$msg\n";
}

say $socket "GETINFO accounting/bytes";
my $down = 0;
my $up = 0;
my $replyline = <$socket>;
chomp($replyline);
if ($replyline =~ /^250-accounting\/bytes=(\d+)\s(\d+)/) {
    $down = $1;
    $up = $2;
} else {
    die "Failed to get accounting info: $replyline\n";
}

say $socket "QUIT";
close($socket);

say "down.value $down";
say "up.value $up";

exit 0;
