package eyelib::snmp;

#
# Copyright (C) Roman Dmitiriev, rnd@rajven.ru
#

use utf8;
use open ":encoding(utf8)";
use strict;
use English;
use FindBin '$Bin';
use lib "/opt/Eye/scripts";
use base 'Exporter';
use vars qw(@EXPORT @ISA);
use Data::Dumper;
use eyelib::config;
use eyelib::main;
use Net::SNMP;

@ISA = qw(Exporter);
@EXPORT = qw(
snmp_set_int
snmp_get_request
get_arp_table
get_fdb_table
get_mac_table
get_vlan_at_port
get_switch_vlans
get_snmp_ifindex
get_ifmib_index_table
getIpAdEntIfIndex
get_interfaces
get_router_state
snmp_get_req
snmp_get_oid
snmp_walk_oid
table_callback
$ifAlias
$ifName
$ifDescr
$ifIndex
$ifIndex_map
$arp_oid
$ipNetToMediaPhysAddress
$ipAdEntIfIndex
$fdb_table_oid
$fdb_table_oid2
$cisco_vlan_oid
$dot1qPortVlanEntry
$fdb_table;
$snmp_timeout
setCommunity
init_snmp
);


BEGIN
{

our $ifAlias        ='.1.3.6.1.2.1.31.1.1.1.18';
our $ifName         ='.1.3.6.1.2.1.31.1.1.1.1';
our $ifDescr        ='.1.3.6.1.2.1.2.2.1.2';
our $ifIndex        ='.1.3.6.1.2.1.2.2.1.1';
our $ifIndex_map    ='.1.3.6.1.2.1.17.1.4.1.2';
our $ipAdEntIfIndex ='.1.3.6.1.2.1.4.20.1.2';

#RFC1213::atPhysAddress
our $arp_oid      ='.1.3.6.1.2.1.3.1.1.2';
#RFC1213::ipNetToMediaPhysAddress
our $ipNetToMediaPhysAddress = '.1.3.6.1.2.1.4.22.1.2';
#RFC1493::dot1dTpFdbTable
our $fdb_table_oid ='.1.3.6.1.2.1.17.4.3.1.2';
#Q-BRIDGE-MIB::dot1qTpFdbPort
our $fdb_table_oid2='.1.3.6.1.2.1.17.7.1.2.2.1.2';
#Q-BRIDGE-MIB::dot1qPortVlanEntry
our $dot1qPortVlanEntry ='.1.3.6.1.2.1.17.7.1.4.5.1.1';
#CISCO-ES-STACK-MIB::
our $cisco_vlan_oid='.1.3.6.1.4.1.9.9.46.1.3.1.1.2';

our $fdb_table;

our $snmp_timeout = 15;

#---------------------------------------------------------------------------------

sub snmp_get_request {
my $ip = shift;
my $oid = shift;
my $snmp = shift;

my $session = init_snmp ($ip,$snmp);
return if (!defined($session) or !$session);

my $result = $session->get_request( -varbindlist => [$oid]);
$session->close;
return if (!$result->{$oid});
return $result->{$oid};
}

#---------------------------------------------------------------------------------

sub snmp_set_int {
my $ip = shift;
my $oid = shift;
my $value = shift;
my $snmp = shift;

my $session = init_snmp ($ip,$snmp,1);
return if (!defined($session) or !$session);

my $result = $session->set_request( -varbindlist => [$oid,INTEGER,$value]);
$session->close;
return $result->{$oid};
}

#-------------------------------------------------------------------------------------

sub get_arp_table {
    my ($host,$snmp) = @_;

    my $session = init_snmp ($host,$snmp,0);
    return if (!defined($session) or !$session);

    $session->translate([-all]);

    my $arp;
    my $arp_table1 = $session->get_table($arp_oid);
    my $arp_table2 = $session->get_table($ipNetToMediaPhysAddress);
    $session->close();

    if ($arp_table1) {
        foreach my $row (keys(%$arp_table1)) {
        my ($mac_h) = unpack("H*",$arp_table1->{$row});
        next if (!$mac_h or $mac_h eq '000000000000' or $mac_h eq 'ffffffffffff');
        my $mac;
        if (length($mac_h)==12) { $mac=lc $mac_h; }
        next if (!$mac);
        $row=trim($row);
        my $ip;
        if ($row=~/\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/) { $ip=$1.".".$2.".".$3.".".$4; }
        next if (!$ip);
        $arp->{$ip}=$mac;
        };
    }

    if ($arp_table2) {
        foreach my $row (keys(%$arp_table2)) {
        my ($mac_h) = unpack("H*",$arp_table2->{$row});
        next if (!$mac_h or $mac_h eq '000000000000' or $mac_h eq 'ffffffffffff');
        my $mac;
        if (length($mac_h)==12) { $mac=lc $mac_h; }
        next if (!$mac);
        $row=trim($row);
        my $ip;
        if ($row=~/\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/) { $ip=$1.".".$2.".".$3.".".$4; }
        next if (!$ip);
        $arp->{$ip}=$mac;
        };
    }

    return $arp;
}

#-------------------------------------------------------------------------------------

sub get_ifmib_index_table {
my $ip = shift;
my $snmp = shift;
my $ifmib_map;

my $is_mikrotik = snmp_get_request($ip, '.1.3.6.1.2.1.9999.1.1.1.1.0', $snmp);
my $mk_ros_version = 0;

if ($is_mikrotik=~/MikroTik/i) {
    my $mikrotik_version = snmp_get_request($ip, '.1.0.8802.1.1.2.1.3.4.0', $snmp);
    $mk_ros_version = 6491;
    #"MikroTik RouterOS 6.46.8 (long-term) CRS326-24S+2Q+"
    if ($mikrotik_version =~/RouterOS\s+(\d)\.(\d{1,3})\.(\d{1,3})\s+/) {
        $mk_ros_version = $1*1000 + $2*10 + $3;
        }
    }

if (!$mk_ros_version or $mk_ros_version > 6468) {
    my $index_map_table =  snmp_get_oid($ip, $snmp, $ifIndex_map);
    if (!$index_map_table) { $index_map_table =  snmp_walk_oid($ip, $snmp, $ifIndex_map); }
    if ($index_map_table) {
        foreach my $row (keys(%$index_map_table)) {
            my $port_index = $index_map_table->{$row};
            next if (!$port_index);
                my $value;
            if ($row=~/\.([0-9]{1,10})$/) { $value = $1; }
            next if (!$value);
            $ifmib_map->{$value}=$port_index;
            }
        }
    }

if (!$ifmib_map) {
    my $index_table =  snmp_get_oid($ip, $snmp, $ifIndex);
    if (!$index_table) { $index_table =  snmp_walk_oid($ip, $snmp, $ifIndex); }
    foreach my $row (keys(%$index_table)) {
            my $port_index = $index_table->{$row};
            next if (!$port_index);
        my $value;
        if ($row=~/\.([0-9]{1,10})$/) { $value = $1; }
            next if (!$value);
        $ifmib_map->{$value}=$value;
        };
    }
return $ifmib_map;
}

#-------------------------------------------------------------------------------------

sub get_mac_table {
    my ($host,$snmp,$oid,$index_map) = @_;
    my $fdb;
    #need for callback
    $fdb_table=$oid;
    my $fdb_table1 = snmp_get_oid($host,$snmp,$oid);
    if (!$fdb_table1) { $fdb_table1=snmp_walk_oid($host,$snmp,$oid,undef); }
    if ($fdb_table1) {
        foreach my $row (keys(%$fdb_table1)) {
                my $port_index = $fdb_table1->{$row};
            next if (!$port_index);
                my $mac;
                if ($row=~/\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/) {
                    $mac=sprintf "%02x%02x%02x%02x%02x%02x",$1,$2,$3,$4,$5,$6;
                    }
            next if (!$mac);
            if ($index_map and exists $index_map->{$port_index}) { $port_index = $index_map->{$port_index}; }
                $fdb->{$mac}=$port_index;
                };
        return $fdb;
            }
}

#-------------------------------------------------------------------------------------

sub get_fdb_table {
    my ($host,$snmp,$iflist) = @_;

    my $ifindex_map = get_ifmib_index_table($host,$snmp);
    my $fdb1=get_mac_table($host,$snmp,$fdb_table_oid,$ifindex_map);
    my $fdb2=get_mac_table($host,$snmp,$fdb_table_oid2,$ifindex_map);

    my $fdb3;

    if ($fdb2 and $iflist) {
        foreach my $mac (keys %$fdb2) {
            if (exists $iflist->{$fdb2->{$mac}}) { $fdb3->{$mac}=$iflist->{$fdb2->{$mac}}; }
            }
        }

    my $fdb;
    if ($fdb1 and !$fdb3) { $fdb = $fdb1; }
    if (!$fdb1 and $fdb3) { $fdb = $fdb3; }
    #join tables
    if ($fdb1 and $fdb3) { $fdb = { %$fdb1,%$fdb3 }; }

    my $snmp_cisco = $snmp;

    #maybe cisco?!
    if (!$fdb) {
        my $vlan_table=snmp_get_oid($host,$snmp,$cisco_vlan_oid);
        if (!$vlan_table) { $vlan_table=snmp_walk_oid($host,$snmp,$cisco_vlan_oid); }
        #fuck!
        if (!$vlan_table) { return; }
        my %fdb_vlan;
            foreach my $vlan_oid (keys %$vlan_table) {
                next if (!$vlan_oid);
                my $vlan_id;
                if ($vlan_oid=~/\.([0-9]{1,4})$/) { $vlan_id=$1; }
                next if (!$vlan_id);
                next if ($vlan_id>1000 and $vlan_id<=1009);
                $snmp_cisco->{'ro-community'} = $snmp->{'ro-community'}.'@'.$vlan_id;
                $fdb_vlan{$vlan_id}=get_mac_table($host,$snmp_cisco,$fdb_table_oid,$ifindex_map);
                if (!$fdb_vlan{$vlan_id}) { $fdb_vlan{$vlan_id}=get_mac_table($host,$snmp_cisco,$fdb_table_oid2,$ifindex_map); }
            }
            foreach my $vlan_id (keys %fdb_vlan) {
                next if (!exists $fdb_vlan{$vlan_id});
                if (defined $fdb_vlan{$vlan_id}) {
                        my %tmp=%{$fdb_vlan{$vlan_id}};
                        foreach my $mac (keys %tmp) {
                                next if (!$mac);
                                $fdb->{$mac}=$tmp{$mac};
                                }
                    }
            }
        }
    return $fdb;
}

#-------------------------------------------------------------------------------------

sub get_vlan_at_port {
    my ($host,$snmp,$port_index) = @_;
    my $vlan_oid=$dot1qPortVlanEntry.".".$port_index;
    my $vlan = snmp_get_req($host,$snmp,$vlan_oid);
    return "1" if (!$vlan);
    return "1" if ($vlan=~/noSuchObject/i);
    return "1" if ($vlan=~/noSuchInstance/i);
    return $vlan;
}

#-------------------------------------------------------------------------------------

sub get_switch_vlans {
    my ($host,$snmp) = @_;
    my $result;
    #need for callback
    my $vlan_table = snmp_get_oid($host,$snmp,$dot1qPortVlanEntry);
    if (!$vlan_table) { $vlan_table=snmp_walk_oid($host,$snmp,$dot1qPortVlanEntry); }
    if ($vlan_table) {
        foreach my $vlan_oid (keys %$vlan_table) {
            if ($vlan_oid=~/\.([0-9]*)$/) { $result->{$1} = $vlan_table->{$vlan_oid}; }
            }
        }
    return $result;
}

#-------------------------------------------------------------------------------------

sub get_snmp_ifindex {
    my ($host,$snmp) = @_;
    my $session = init_snmp($host,$snmp,0);
    return if (!defined($session) or !$session);

    my $if_index = $session->get_table($ifIndex);
    my $result;
    foreach my $row (keys(%$if_index)) {
        my $value = $if_index->{$row};
        $row=~s/^$ifIndex\.//;
        $result->{$row}=$value;
        };
    $session->close();
    return $result;
}

#-------------------------------------------------------------------------------------

#get ip interfaces
sub getIpAdEntIfIndex {
    my ($host,$snmp) = @_;
    my $session = init_snmp ($host,$snmp,0);
    return if (!defined($session) or !$session);

    $session->translate([-timeticks]);
    my $if_ipaddr = $session->get_table($ipAdEntIfIndex);
    my $l3_list;
    foreach my $row (keys(%$if_ipaddr)) {
        my $ipaddr = $row;
        $ipaddr=~s/$ipAdEntIfIndex\.//;
        $l3_list->{$ipaddr}=$if_ipaddr->{$row};
    }
    $session->close();
    return $l3_list;
}

#-------------------------------------------------------------------------------------

sub get_interfaces {
    my ($host,$snmp,$skip_empty) = @_;

    my $session = init_snmp ($host,$snmp,0);
    return if (!defined($session) or !$session);

    $session->translate([-timeticks]);
    my $if_name = $session->get_table($ifName);
    my $if_alias = $session->get_table($ifAlias);
    my $if_descr = $session->get_table($ifDescr);
    my $if_index = $session->get_table($ifIndex);
    $session->close();
    my $dev_cap;

    foreach my $row (keys(%$if_index)) {
    my $index = $if_index->{$row};
    next if ($if_name->{$ifName.".".$index} =~/^lo/i);
    next if ($if_name->{$ifName.".".$index} =~/^dummy/i);
    next if ($if_name->{$ifName.".".$index} =~/^enet/i);
    next if ($if_name->{$ifName.".".$index} =~/^Nu/i);
#    next if ($if_name->{$ifName.".".$index} =~/^Po/i);
    my $ifc_alias;
    $ifc_alias=$if_alias->{$ifAlias.".".$index} if ($if_alias->{$ifAlias.".".$index});
    my $ifc_name;
    $ifc_name=$if_name->{$ifName.".".$index} if ($if_name->{$ifName.".".$index});
    my $ifc_desc;
    $ifc_desc=$if_descr->{$ifDescr.".".$index} if ($if_descr->{$ifDescr.".".$index});

    $dev_cap->{$index}->{alias}=$ifc_alias if ($ifc_alias);
    $dev_cap->{$index}->{name}=$ifc_name if ($ifc_name);
    $dev_cap->{$index}->{desc}=$ifc_desc if ($ifc_desc);
    $dev_cap->{$index}->{index} = $index;
    };
    return $dev_cap;
}

#-------------------------------------------------------------------------------------

sub get_router_state {
    my ($host,$snmp,$skip_empty) = @_;
    my $session = init_snmp ($host,$snmp,0);
    return if (!defined($session) or !$session);
    $session->translate([-timeticks]);
    my $router_status = $session->get_table("1.3.6.1.4.1.10.1");
    $session->close();
    return ($router_status);
}

#-------------------------------------------------------------------------------------

sub snmp_get_req {
my ($host,$snmp,$oid) = @_;
my $session = init_snmp ($host,$snmp,0);
return if (!defined($session) or !$session);
$session->translate([-timeticks]);
my $result = $session->get_request(-varbindlist => [$oid]) or return;
$session->close();
return $result->{$oid};
}

#-------------------------------------------------------------------------------------

sub snmp_get_oid {
my ($host,$snmp,$oid) = @_;
my $port = 161;
my $session = init_snmp ($host,$snmp,0);
return if (!defined($session) or !$session);
$session->translate([-timeticks]);
my $table = $session->get_table($oid);
$session->close();
return $table;
}

#-------------------------------------------------------------------------------------

sub snmp_walk_oid {

my $host = shift;
my $snmp = shift;
my $oid = shift;
my $rw = 'ro';

### open SNMP session
my ($session, $error);

if ($snmp->{version} <= 2) {
        ($session, $error) = Net::SNMP->session(
		-hostname  => $host,
		-community => $snmp->{'ro-community'} ,
		-version   => $snmp->{version},
		-port      => $snmp->{port},
		-timeout   => $snmp->{timeout},
		-nonblocking => 1,
		-translate   => [-octetstring => 0],
		);
	} else {
	($session, $error) = Net::SNMP->session(
		-hostname     => $host,
		-version      => 'snmpv3',
		-username     => $snmp->{$rw.'-user'},
		-authprotocol => $snmp->{'auth-proto'},
		-privprotocol => $snmp->{'priv-proto'},
		-authpassword => $snmp->{$rw.'-password'},
		-privpassword => $snmp->{$rw.'-password'},
		-port         => $snmp->{port},
		-timeout      => $snmp->{timeout},
		-nonblocking  => 1,
		-translate    => [-octetstring => 0],
		);
	}

return if (!defined($session) or !$session);

my %table; # Hash to store the results

my $result = $session->get_bulk_request(
      -varbindlist    => [ $oid ],
      -callback       => [ \&table_callback, \%table ],
      -maxrepetitions => 10,
   );

snmp_dispatcher();
$session->close();

return \%table;
}

#-------------------------------------------------------------------------------------

sub table_callback  {
my ($session, $table) = @_;
my $list = $session->var_bind_list();

if (!defined $list) {
    printf "ERROR: %s\n", $session->error();
    return;
    }

my @names = $session->var_bind_names();
my $next  = undef;

while (@names) {
    $next = shift @names;
    if (!oid_base_match($fdb_table, $next)) { return; }
    $table->{$next} = $list->{$next};
    }

my $result = $session->get_bulk_request( -varbindlist    => [ $next ], -maxrepetitions => 10);

if (!defined $result) {
     printf "ERROR: %s.\n", $session->error();
    }
return;
}


#-------------------------------------------------------------------------------------

sub init_snmp {

    my ($host,$snmp,$rw) = @_;

    return if (!$host);

    my $community = $snmp->{'ro-community'};
    if (!$rw) { $rw = 'ro' }
	    else {
	    $rw = 'rw';
	    $community = $snmp->{'rw-community'};
	    }

    ### open SNMP session
    my ($session, $error);

    if ($snmp->{version} <=2) {
        ($session, $error) = Net::SNMP->session(
		-hostname  => $host,
		-community => $community ,
		-version   => $snmp->{'version'},
		-port      => $snmp->{port},
		-timeout   => $snmp->{timeout},
		);
	} else {
	($session, $error) = Net::SNMP->session(
		-hostname     => $host,
		-version      => 'snmpv3',
		-username     => $snmp->{$rw.'-user'},
		-authprotocol => $snmp->{'auth-proto'},
		-privprotocol => $snmp->{'priv-proto'},
		-authpassword => $snmp->{$rw.'-password'},
		-privpassword => $snmp->{$rw.'-password'},
		-port         => $snmp->{port},
		-timeout      => $snmp->{timeout},
		);
	}
    if ($error) {
        log_debug("SNMP init-request status for $host:");
        log_debug(Dumper($error));
        }
    return if (!defined($session) or !$session);
    return $session;
}

#-------------------------------------------------------------------------------------

sub setCommunity {
my $device = shift;
$device->{snmp}->{'port'}         = 161;
$device->{snmp}->{'timeout'}      = $snmp_timeout;
$device->{snmp}->{'version'}      = $device->{snmp_version} || '2';
$device->{snmp}->{'ro-community'} = $device->{community} || $snmp_default_community;
$device->{snmp}->{'rw-community'} = $device->{rw_community} || $snmp_default_community;
#snmpv3
$device->{snmp}->{'auth-proto'}   = $device->{snmp3_auth_proto} || 'sha512';
$device->{snmp}->{'priv-proto'}   = $device->{snmp3_priv_proto} || 'aes128';
$device->{snmp}->{'ro-user'}      = $device->{snmp3_user_ro} || '';
$device->{snmp}->{'rw-user'}      = $device->{snmp3_user_rw} || '';
$device->{snmp}->{'ro-password'}  = $device->{snmp3_user_ro_password} || $snmp_default_community;
$device->{snmp}->{'rw-password'}  = $device->{snmp3_user_rw_password} || $snmp_default_community;
}

#-------------------------------------------------------------------------------------

1;
}
