#!/usr/bin/perl

# tc-snmp.pl
# a bridge between the iproute2 tool tc and snmp
#
# sources:
#   snmp.pl     http://www.docum.org/stef.coene/qos/gui/
#   trafgraf.pl by Sébastien Cramatte <scramatte@zensoluciones.com> October 2006
#
# this version: Roman 'winex' Vasiyarov <rvasiyarov@gmail.com>

use strict;
use warnings;

sub nics();
sub disp($$);


#------------------------------------------------------------#
my $TC = "/sbin/tc";
my $IP = "/sbin/ip";

my $cache_secs = 60;
my $mibtime = 0;

my ($mib, $oid_in, $oid, $found);

#my ($key, %bytes, %lended);

my @nics = nics();


#------------------------------------------------------------#
# figure out what to send back

# Switch on autoflush
$| = 1;

# TODO: 20100902 winex: remove .253 here and get it automagically from input
my $baseoid = '.1.3.6.1.4.1.2021.253';


#
# main loop
#
while (<STDIN>)
{
	if (/^PING$/)
	{
		print("PONG\n");
		next;
	}

	if (/^get(next)*?$/)
	{
		$oid_in = <STDIN>;

		$oid = get_oid($oid_in);
		$mib = create_mib();

		if (/^getnext$/)
		{
			$found = 0;

			my @s = sort { oidcmp($a, $b) } (keys %{ $mib });
			for (my $i = 0; $i < @s; $i++)
			{
				if (oidcmp($oid, $s[$i]) == -1)
				{
					print("$baseoid.".$s[$i]."\n");
					print($mib->{$s[$i]}[0]."\n");
					print($mib->{$s[$i]}[1]."\n");
					$found = 1;
					last;
				}
			}

			print("NONE\n") if (!$found);
			next;
		}

		if (defined($mib->{$oid}))
		{
			print("$baseoid.$oid\n");
			print($mib->{$oid}[0]."\n");
			print($mib->{$oid}[1]."\n");
			next;
		}

		print("NONE\n");
		next;
	}


	# Unknown command
	next;
}

#------------------------------------------------------------#
# get all the info from all interfaces (qdisc + class)

sub create_mib
{
	my (%qos, %tokens);
	my (@qdisc, @class, $s, $if, $ifNum);
	my (%parent, %classdatas, $num, %tmp, %oids);

	return $mib if (time - $mibtime < $cache_secs);

	for ($ifNum = 0; $ifNum < @nics; $ifNum++)
	{
		$if = $nics[$ifNum];

		@qdisc = `$TC -s qdisc show dev $if`;
		@class = `$TC -s class show dev $if`;

		$num = "";

		my $ifNumSnmp = $ifNum + 1;
		my $numSnmp = 0;

		$qos{"1.1.$ifNumSnmp"} = ["string", "INTERFACE"];
		$qos{"1.1.$ifNumSnmp.1"} = ["string", $if];

		foreach $s (@qdisc)
		{
			$s =~ /^\s*$/
				and next;

			# get qdisc id
			# qdisc sfq 8045: quantum 1514b limit 128p flows 128/1024
			($num) = $s =~ /^qdisc [^\s]+ ([A-Za-z0-9]+): /
				and next;

			# Sent 0 bytes 0 pkts (dropped 0, overlimits 0 requeues 0)
			$s =~ /^ Sent (\d+) bytes (\d+) pkt \(dropped (\d+), overlimits (\d+) requeues (\d+)\)/
				and $numSnmp++
				and $qos{"2.$ifNumSnmp.2.$numSnmp"} = ["counter", adjust_to_32bit($1)]
				and $qos{"2.$ifNumSnmp.3.$numSnmp"} = ["counter", adjust_to_32bit($2)]
				and $qos{"2.$ifNumSnmp.4.$numSnmp"} = ["counter", adjust_to_32bit($3)]
				and $qos{"2.$ifNumSnmp.5.$numSnmp"} = ["counter", adjust_to_32bit($4)]
				and $qos{"2.$ifNumSnmp.6.$numSnmp"} = ["counter", adjust_to_32bit($5)]
				and $num = ""
				and next;

			# rate 0bit 0pps backlog 0b 0p requeues 0
			$s =~ /^ rate/
				and next;
		}

		$num = "";
		$numSnmp = 0;

		foreach $s (@class)
		{
			$s =~ /^\s*$/
				and next;

			#class htb 1:1 root prio 0 rate 16000Kbit ceil 16000Kbit burst 2Kb cburst 22077b
			# If we have a root class, the parent = "major_number:"
			$s =~ /^[^\s]+ ([^\s]+) ([A-Za-z0-9]+:[A-Za-z0-9]+) root/
				and $numSnmp++
				and $num = $2
				and $qos{"3.$ifNumSnmp.1.$numSnmp"} = ["string", $num]
				and $qos{"3.$ifNumSnmp.2.$numSnmp"} = ["string", $3]
				and next;

			#class htb 1:100 parent 1:10 leaf 8002: prio 0 rate 8000Kbit ceil 8000Kbit burst 2Kb cburst 11838b
			$s =~ /^[^\s]+ ([^\s]+) ([A-Za-z0-9]+:[A-Za-z0-9]+) parent ([A-Za-z0-9]+:[A-Za-z0-9]+) leaf ([A-Za-z0-9]+:) prio/
				and $numSnmp++
				and $num = $2
				and $qos{"3.$ifNumSnmp.1.$numSnmp"} = ["string", $num]
				and $qos{"3.$ifNumSnmp.2.$numSnmp"} = ["string", $3]
				and next;

			#class htb 1:10 parent 1:1 prio 0 rate 8000Kbit ceil 8000Kbit burst 2Kb cburst 11838b
			$s =~ /^[^\s]+ ([^\s]+) ([A-Za-z0-9]+:[A-Za-z0-9]+) parent ([A-Za-z0-9]+:[A-Za-z0-9]+) prio/
				and $numSnmp++
				and $num = $2
				and $qos{"3.$ifNumSnmp.1.$numSnmp"} = ["string", $num]
				and $qos{"3.$ifNumSnmp.2.$numSnmp"} = ["string", $3]
				and next;

			# Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0)
			$s =~ /^ Sent (\d+) bytes (\d+) pkt \(dropped (\d+), overlimits (\d+) requeues (\d+)\)/
				and $qos{"3.$ifNumSnmp.3.$numSnmp"} = ["counter", adjust_to_32bit($1)]
				and $qos{"3.$ifNumSnmp.4.$numSnmp"} = ["counter", adjust_to_32bit($2)]
				and $qos{"3.$ifNumSnmp.5.$numSnmp"} = ["counter", adjust_to_32bit($3)]
				and $qos{"3.$ifNumSnmp.6.$numSnmp"} = ["counter", adjust_to_32bit($4)]
				and $qos{"3.$ifNumSnmp.7.$numSnmp"} = ["counter", adjust_to_32bit($5)]
				and next;

			# rate 0bit 0pps backlog 0b 0p requeues 0
			$s =~ /^ rate/
				and next;

			# lended: 0 borrowed: 0 giants: 0
			$s =~ /^ lended: (\d+) borrowed: (\d+) giants: (\d+)/
				and $qos{"3.$ifNumSnmp.8.$numSnmp"}  = ["counter", adjust_to_32bit($1)]
				and $qos{"3.$ifNumSnmp.9.$numSnmp"}  = ["counter", adjust_to_32bit($2)]
				and $qos{"3.$ifNumSnmp.10.$numSnmp"} = ["counter", adjust_to_32bit($3)]
				and next;

			# tokens: 1637 ctokens: 9471
			$s =~ /^ tokens: (\d+) ctokens: (\d+)/
				and $qos{"3.$ifNumSnmp.11.$numSnmp"} = ["counter", adjust_to_32bit($1)]
				and $qos{"3.$ifNumSnmp.12.$numSnmp"} = ["counter", adjust_to_32bit($2)]
				and next;
		}
	}

	$mib = \%qos;
	$mibtime = time;
	return $mib;
}

sub create_coid
{
	my ($k, %p, %oids) = @_;
	my ($ifoid,$val) = split(/\s/, $k);

	my $id  = $p{$val};
	my $oid = "";

	while (defined($id))
	{
		$oid = $oids{$id} ? $oids{$id}++ : 1;
		print("$oid\n");
		$id = $p{$id};
	}

	return "";
}


sub adjust_to_32bit
{
	my ($val) = @_;
	if ($val > 4294967295) { $val = $val % 4294967295; }
	return $val;
}

sub get_oid
{
	my ($oid) = @_;
	my $base = $baseoid;
	$base =~ s/\./\\./g;

	chomp($oid);
	if ($oid !~ /^$base(\.|$)/)
	{
		# Requested oid doesn't match base oid
		return 0;
	}

	$oid =~ s/^$base\.?//;
	return $oid;
}


sub oidcmp
{
	my ($x, $y) = @_;
	my @a = split(/\./, $x);
	my @b = split(/\./, $y);
	my $i = 0;

	while (1)
	{
		if ($i > $#a)
		{
			return  0 if ($i > $#b);
			return -1;
		}
		return  1 if ($i > $#b);

		return -1 if ($a[$i] < $b[$i]);
		return  1 if ($a[$i] > $b[$i]);

		$i++;
	}
}

#------------------------------------------------------------#
# Find all configured nics
sub nics()
{
	my @niclist = `$IP link show`;
	my (@nic, $s);
	foreach $s (@niclist)
	{
		$s =~ /^([0-9]+):\s+([a-z0-9]+)/ or next;
		push(@nic, $2);
	}

	return(@nic);
}
