#!/usr/bin/perl -w
#
# tor_bandwidth_acct - munin plugin to monitor Tor routers 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
#
# tested with Tor releases: 0.2.1.28, 0.2.1.29
#
# 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/node.d/ or in /etc/munin/plugins (or link it there using ln -s)
#
# Parameters understood:
# 	config   (required)
# 	autoconf (optional - used by munin-config)
#
# todo:
# try using "graph_period" option "to make graph_sums usable"
#
# Magic markers - optional - used by installation scripts and
# munin-config:
#
#%# family=contrib
#%# capabilities=autoconf

use strict;
use IO::Socket::INET;

# Config
our $address = $ENV{host}  || "localhost";	# Default: localhost
our $port    = $ENV{port}  || 9051;		# Default: 9051

# Don't edit below this line

sub Authenticate
{
	my ($socket) = @_;
	my $authline = "AUTHENTICATE";
	if (defined($ENV{cookiefile})) {
		if (open(COOKIE, "<$ENV{cookiefile}")) {
			binmode COOKIE;
			my $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} . '"';
	}
	print $socket "$authline\r\n";
	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) {
		print "no (failed to connect to $address port $port)\n";
		exit 1;
	}

	my $msg = Authenticate($socket);
	if (defined($msg)) {
		print $socket "QUIT\r\n";
		close($socket);
		print "no ($msg)\n";
		exit 1;
	}

	print $socket "QUIT\r\n";
	close($socket);
	print "yes\n";
	exit 0;
}

if ($ARGV[0] and $ARGV[0] eq "config") {
	print "graph_title Tor bandwidth usage (in/out)\n";
	print "graph_args --base 1000\n";
	print "graph_vlabel bytes/sec\n";
	print "graph_category Tor\n";
	print "graph_info This graph shows the flowing incoming/outgoing bytes on a Tor node\n";
	print "down.label Download\n";
        print "down.type DERIVE\n";
        print "down.min 0\n";
        print "up.label Upload\n";
	print "up.type DERIVE\n";
	print "up.min 0\n";

        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)) {
	print $socket "QUIT\r\n";
	close($socket);
	die "$msg\n";
}

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

print $socket "QUIT\r\n";
close($socket);

print "down.value $down\n";
print "up.value $up\n";

exit 0;
