#!/usr/bin/perl -w
#
# Copyright (c) IBM Corp. 2009, 2010, All Rights Reserved
#
# Author(s):	Vasileios Pappas <vpappas@us.ibm.com>
#		Jens Osterkamp <jens@linux.vnet.ibm.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

use NetSNMP::OID (':all');
use NetSNMP::ASN (':all');
use NetSNMP::agent (':all');

my %oid_value;
my %oid_type;
my %oid_next;
my $agent;
my $subagent=0;
my $netdir="/sys/class/net/";

$cache_timout=60;
$last_populated=0;

my $regoid = '.1.3.6.1.2.1.17';

print STDERR "registering at ".$regoid."\n";

# are we running embedded ? If not, register as subagent
if (!$agent) {
    $agent = new NetSNMP::agent('Name' => 'dot1qbridge',
				'AgentX' => 1);
    $subagent = 1;
    print STDERR "running as a subagent.\n";
}

$agent->register('dot1qbridge', $regoid, \&request_handler) or	die "registration of handler failed !\n";

print STDERR "dot1qbridge agent started.\n";

if ($subagent) {
	# register handler for graceful shutdown
	$SIG{'INT'} = \&shutdown;
	$SIG{'QUIT'} = \&shutdown;
	$running = 1;

	while($running) {
		$agent->agent_check_and_process(1);
	}

	$agent->shutdown();
}

sub request_handler{
	my ($handler, $registration_info, $request_info, $requests)=@_;

	populate_mib();

	for($request=$requests; $request; $request=$request->next()){
		if($request_info->getMode()==MODE_GET){
			mode_get($request);
		}elsif($request_info->getMode()==MODE_GETNEXT){
			mode_get_next($request);
		}else{

		}
	}
}

sub mode_get{
	my ($request)=@_;
	my $oid=$request->getOID();

	reply($request, $oid) if(exists $oid_value{$oid});
}

sub mode_get_next{
	my ($request)=@_;
	my $oid=$request->getOID();

	if(defined($oid_next{$oid})){
		reply($request, $oid_next{$oid});
	}elsif($oid<=new NetSNMP::OID(".1.3.6.1.2.1.17.1.4.1.1")){
		reply($request,
		$oid_next{new NetSNMP::OID(".1.3.6.1.2.1.17.1.4.1.1")});
	}elsif($oid<=new NetSNMP::OID(".1.3.6.1.2.1.17.1.4.1.2")){
		reply($request,
		$oid_next{new NetSNMP::OID(".1.3.6.1.2.1.17.1.4.1.2")});
	}elsif($oid<=new NetSNMP::OID(".1.3.6.1.2.1.17.4.3.1.1")){
		reply($request,
		$oid_next{new NetSNMP::OID(".1.3.6.1.2.1.17.4.3.1.1")});
	}elsif($oid<=new NetSNMP::OID(".1.3.6.1.2.1.17.4.3.1.2")){
		reply($request,
		$oid_next{new NetSNMP::OID(".1.3.6.1.2.1.17.4.3.1.2")});
	}elsif($oid<=new NetSNMP::OID(".1.3.6.1.2.1.17.4.3.1.3")){
		reply($request,
		$oid_next{new NetSNMP::OID(".1.3.6.1.2.1.17.4.3.1.3")});
	}else{

	}
}

sub reply{
	my ($request, $oid)=@_;
	my $type=$oid_type{$oid};
	my $value=$oid_value{$oid};

	if ($type and $value) {
		$request->setOID($oid);
		$request->setValue($type, $value);
	}
}

# Populated MIB OID
#
# dot1dTpFdbAddress:	1.3.6.1.2.1.17.4.3.1.1.<dot1dTpFdbAddress>
#			OCTET STRING (SIZE (6))
#
# dot1dTpFdbPort:	1.3.6.1.2.1.17.4.3.1.2.<dot1dTpFdbAddress>
#			INTEGER32
#
# dot1dTpFdbStatus:	1.3.6.1.2.1.17.4.3.1.3.<dot1dTpFdbAddress>
#			INTEGER
#			1 : other
#			2 : invalid
#			3 : learned
#			4 : self
#			5 : mgmt
#
# dot1dBasePort:	1.3.6.1.2.1.17.1.4.1.1.<dot1dBasePort>
#			INTEGER32
#
# dot1dBasePortIfIndex:	1.3.6.1.2.1.17.1.4.1.2.<dot1dBasePort>
#			INTEGER32
#

sub populate_mib{
	my ($now, $oid, $baseport, @bridges);
	my (%indexes, %macs, %ages, %locals);
	my ($prv_baseport, $first_baseport);

        $now=time;
        return if($now-$last_populated<=$cache_timout);
        $last_populated=$now;

	%oid_value=();
	%oid_type=();
	%oid_next=();

	print STDERR "populating MIB\n";

	opendir(DIR, $netdir) or die "unable to open $netdir !\n";
	while(my $br=readdir(DIR)){
		next if $br eq ".";
		next if $br eq "..";
		next unless -d $netdir.$br."/bridge";
		push @bridges, $br;
	}
	close(DIR);

	foreach my $bridge (@bridges){
		my $brifdir=$netdir.$bridge."/brif/";
		opendir(DIR, $brifdir) or return -1;
		my @interfaces = readdir(DIR);
		next if ($#interfaces lt 2);
		foreach my $if (@interfaces) {
			next if $if eq ".";
			next if  $if eq "..";
			my $port_no=$brifdir.$if."/port_no";
			my $ifindex=$netdir.$if."/ifindex";
			open(PRT, $port_no) or return -1;
			my $port=<PRT>;
			close(PRT);
			open(PRT, $ifindex) or return -1;
			my $index=<PRT>;
			close(PRT);
			chomp $port;
			chomp $index;
			$port=hex($port);
			$indexes{$bridge}{$port}=$index;
			$ifindex=$netdir.$bridge."/ifindex";
			open(PRT, $ifindex) or return -1;
			$index=<PRT>;
			close(PRT);
			chomp $index;
			$indexes{$bridge}{$bridge}=$index;
		}
		close(DIR);

		my $fdb=$netdir.$bridge."/brforward";
		open(FWD, $fdb) or return -1;
		while(sysread(FWD, $data, 20)){
			my $mac;
			my ($b1,$b2,$b3,$b4,$b5,$b6,$port,$local,$age,$hi)=
			unpack("C6 C C L C x3", $data);
			$mac=sprintf("%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",
						$b1,$b2,$b3,$b4,$b5,$b6);
			$age=$age/100;
			$macs{$mac}{$bridge}=$port;
			$ages{$mac}{$bridge}=$age;
			$locals{$mac}{$bridge}=3;
			next unless($local);
			$macs{$mac}{$bridge}=$bridge;
			$locals{$mac}{$bridge}=4;
		}
		close(FWD);
	}

	#
	# TODO: fix the way OIDs are created (use only NetSNMP::OID);
	#
	$baseport=1;
	$oid=new NetSNMP::OID(".1.3.6.1.2.1.17.1.4.1");
	foreach my $bridge (keys %indexes){
		foreach my $port (keys %{$indexes{$bridge}}){
			unless(defined($first_baseport)){
				$first_baseport=$baseport;
				$oid_next{$oid.".1"}=$oid.".1.".$baseport;
				$oid_next{$oid.".2"}=$oid.".2.".$baseport;
			}
			my $index=$indexes{$bridge}{$port};
			$baseports{$bridge}{$port}=$baseport;

			$oid_value{$oid.".1.".$baseport}=$baseport;
			$oid_type{$oid.".1.".$baseport}=ASN_INTEGER;
			if(defined($prv_baseport)){
				$oid_next{$oid.".1.".$prv_baseport}=
					$oid.".1.".$baseport;
			}

			$oid_value{$oid.".2.".$baseport}=$index;
			$oid_type{$oid.".2.".$baseport}=ASN_INTEGER;
			if(defined($prv_baseport)){
				$oid_next{$oid.".2.".$prv_baseport}=
					$oid.".2.".$baseport;
			}

			$prv_baseport=$baseport;
			$baseport++;
		}
	}
	$oid_next{$oid.".1.".$prv_baseport}=$oid.".2.".$first_baseport;
	$oid_next{$oid.".2.".$prv_baseport}=0;

	$oid=new NetSNMP::OID(".1.3.6.1.2.1.17.4.3.1");
	foreach my $mac (sort {$a cmp $b} keys %macs){
		foreach my $bridge (keys %{$macs{$mac}}){
			my $mac_oid=mac2oid($mac);
			unless(defined($first_mac_oid)){
				$first_mac_oid=$mac_oid;
				$oid_next{$oid.".1"}=$oid.".1".$mac_oid;
				$oid_next{$oid.".2"}=$oid.".2".$mac_oid;
				$oid_next{$oid.".3"}=$oid.".3".$mac_oid;
			}
			my $port=$macs{$mac}{$bridge};
			my $baseport=$baseports{$bridge}{$port};
			my $status=$locals{$mac}{$bridge};
			my $age=$ages{$mac}{$bridge};

			$oid_value{$oid.".1".$mac_oid}=mac2hex($mac);
			$oid_type{$oid.".1".$mac_oid}=ASN_OCTET_STR;
			if(defined($prv_mac_oid)){
				$oid_next{$oid.".1".$prv_mac_oid}=
					$oid.".1".$mac_oid;
			}

			$oid_value{$oid.".2".$mac_oid}=$baseport;
			$oid_type{$oid.".2".$mac_oid}=ASN_INTEGER;
			if(defined($prv_mac_oid)){
				$oid_next{$oid.".2".$prv_mac_oid}=
					$oid.".2".$mac_oid;
			}

			$oid_value{$oid.".3".$mac_oid}=$status;
			$oid_type{$oid.".3".$mac_oid}=ASN_INTEGER;
			if(defined($prv_mac_oid)){
				$oid_next{$oid.".3".$prv_mac_oid}=
					$oid.".3".$mac_oid;
			}

			$prv_mac_oid=$mac_oid;
		}
	}
	$oid_next{$oid.".1".$prv_mac_oid}=$oid.".2".$first_mac_oid;
	$oid_next{$oid.".2".$prv_mac_oid}=$oid.".3".$first_mac_oid;
	$oid_next{$oid.".3".$prv_mac_oid}=0;

	undef $first_baseport;
	undef $prv_baseport;
	undef $first_mac_oid;
	undef $prv_mac_oid;
	return 0;
}

sub mac2oid{
	my ($mac)=@_;
	my @octets=split(/:/,$mac);

	$mac="";
	foreach my $octet (@octets){
		$mac=$mac.".".hex($octet);
	}
	return $mac;
}

sub mac2hex{
        my ($mac)=@_;
	my @decimals;
	my @octets=split(/:/,$mac);

	foreach my $octet (@octets){
		push @decimals, hex($octet);
	}
	return pack("cccccc", @decimals);
}

sub shutdown {
	$running = 0;
}
