|
|
@@ -197,34 +197,43 @@ sub get_arp_table {
|
|
|
#---------------------------------------------------------------------------------
|
|
|
|
|
|
sub snmp_ping {
|
|
|
- my ($host,$snmp) = @_;
|
|
|
-
|
|
|
+ my ($host, $snmp) = @_;
|
|
|
my @test_oids = (
|
|
|
- '.1.3.6.1.2.1.1.1.0', # model
|
|
|
- '.1.3.6.1.2.1.1.3.0', # uptime
|
|
|
- '.1.3.6.1.2.1.1.5.0', # name
|
|
|
+ '.1.3.6.1.2.1.1.1.0', # sysDescr (model)
|
|
|
+ '.1.3.6.1.2.1.1.3.0', # sysUpTime (uptime)
|
|
|
+ '.1.3.6.1.2.1.1.5.0', # sysName (hostname)
|
|
|
);
|
|
|
-
|
|
|
my $result;
|
|
|
my $old_sig_alarm = $SIG{ALRM};
|
|
|
-
|
|
|
- $SIG{ALRM} = sub { die "Timeout 5 sec reached.\n" };
|
|
|
- alarm($WAIT_TIME // 5);
|
|
|
-
|
|
|
+ my $fast_snmp = $snmp;
|
|
|
+ $fast_snmp->{timeout}=10;
|
|
|
+ $SIG{ALRM} = sub { die "Timeout ${WAIT_TIME}s reached.\n" };
|
|
|
+ alarm($WAIT_TIME // 11);
|
|
|
eval {
|
|
|
foreach my $oid (@test_oids) {
|
|
|
- $result = snmp_get_request($host,$oid,$snmp);
|
|
|
- last if defined $result;
|
|
|
+ log_debug("SNMP ping: trying $oid on $host");
|
|
|
+ $result = snmp_get_request($host, $oid, $fast_snmp);
|
|
|
+ if (defined $result) {
|
|
|
+ log_debug("SNMP ping: SUCCESS $oid = '$result' on $host");
|
|
|
+ last;
|
|
|
+ }
|
|
|
+ log_debug("SNMP ping: failed $oid on $host");
|
|
|
}
|
|
|
};
|
|
|
my $eval_error = $@;
|
|
|
-
|
|
|
alarm(0);
|
|
|
$SIG{ALRM} = $old_sig_alarm;
|
|
|
-
|
|
|
- return (defined $result && !$eval_error) ? 1 : 0;
|
|
|
+ my $success = (defined $result && !$eval_error) ? 1 : 0;
|
|
|
+ if ($success) {
|
|
|
+ log_debug("SNMP ping: $host is UP");
|
|
|
+ } else {
|
|
|
+ my $reason = $eval_error ? "timeout/error: $eval_error" : "no OID responded";
|
|
|
+ log_warning("SNMP ping: $host is DOWN ($reason)");
|
|
|
+ }
|
|
|
+ return $success;
|
|
|
}
|
|
|
|
|
|
+
|
|
|
#-------------------------------------------------------------------------------------
|
|
|
|
|
|
sub get_ifmib_index_table {
|
|
|
@@ -301,12 +310,10 @@ sub get_mac_table {
|
|
|
|
|
|
sub get_fdb_table {
|
|
|
my ($host,$snmp) = @_;
|
|
|
+
|
|
|
my $ifindex_map = get_ifmib_index_table($host,$snmp);
|
|
|
-# print "IFINDEX_MAP: " . Dumper($ifindex_map);
|
|
|
my $fdb1=get_mac_table($host,$snmp,$fdb_table_oid,$ifindex_map);
|
|
|
-# print "FDB1: " . Dumper($fdb1);
|
|
|
my $fdb2=get_mac_table($host,$snmp,$fdb_table_oid2,$ifindex_map);
|
|
|
-# print "FDB2: " . Dumper($fdb1);
|
|
|
|
|
|
my $fdb;
|
|
|
#join tables
|
|
|
@@ -496,45 +503,45 @@ sub snmp_walk_oid {
|
|
|
$opt ||= {};
|
|
|
|
|
|
my $nonblocking = $opt->{nonblocking} // 1;
|
|
|
- log_debug("Starting SNMP walk on $host, OID: $oid, nonblocking=" . ($nonblocking ? 1 : 0)) if defined &log_debug;
|
|
|
+ log_debug("Starting SNMP walk on $host, OID: $oid, nonblocking=" . ($nonblocking ? 1 : 0));
|
|
|
my $session = init_snmp($host, $snmp, 'ro', $nonblocking);
|
|
|
unless ($session) {
|
|
|
- log_debug("Failed to initialize SNMP session for $host") if defined &log_debug;
|
|
|
+ log_debug("Failed to initialize SNMP session for $host");
|
|
|
return;
|
|
|
}
|
|
|
my %table;
|
|
|
if ($nonblocking) {
|
|
|
# Async walk через callback
|
|
|
- log_debug("Sending first get_bulk_request for OID $oid") if defined &log_debug;
|
|
|
+ log_debug("Sending first get_bulk_request for OID $oid");
|
|
|
my $result = $session->get_bulk_request(
|
|
|
-varbindlist => [$oid],
|
|
|
-callback => [\&table_callback, \%table, $oid, undef],
|
|
|
-maxrepetitions => 10,
|
|
|
);
|
|
|
unless (defined $result) {
|
|
|
- log_debug("SNMP request error ($host): " . $session->error) if defined &log_debug;
|
|
|
+ log_debug("SNMP request error ($host): " . $session->error);
|
|
|
$session->close();
|
|
|
return;
|
|
|
}
|
|
|
# Запускаем dispatcher для обработки async запросов
|
|
|
eval {
|
|
|
- log_debug("Starting snmp_dispatcher for $host") if defined &log_debug;
|
|
|
+ log_debug("Starting snmp_dispatcher for $host");
|
|
|
snmp_dispatcher();
|
|
|
};
|
|
|
if ($@) {
|
|
|
- log_debug("SNMP dispatcher exception ($host): $@") if defined &log_debug;
|
|
|
+ log_debug("SNMP dispatcher exception ($host): $@");
|
|
|
$session->close();
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
# Blocking walk через get_next
|
|
|
- log_debug("Starting blocking SNMP walk for OID $oid") if defined &log_debug;
|
|
|
+ log_debug("Starting blocking SNMP walk for OID $oid");
|
|
|
my $current_oid = $oid;
|
|
|
while (1) {
|
|
|
my $result = $session->get_next_request(-varbindlist => [$current_oid]);
|
|
|
unless (defined $result) {
|
|
|
- log_debug("SNMP request error ($host): " . $session->error) if defined &log_debug;
|
|
|
+ log_debug("SNMP request error ($host): " . $session->error);
|
|
|
last;
|
|
|
}
|
|
|
my $list = $session->var_bind_list();
|
|
|
@@ -542,109 +549,168 @@ sub snmp_walk_oid {
|
|
|
my $stop = 0;
|
|
|
for my $k (keys %$list) {
|
|
|
unless (oid_base_match($oid, $k)) {
|
|
|
- log_debug("OID $k outside root $oid, stopping walk") if defined &log_debug;
|
|
|
+ log_debug("OID $k outside root $oid, stopping walk");
|
|
|
$stop = 1;
|
|
|
last;
|
|
|
}
|
|
|
$table{$k} = $list->{$k};
|
|
|
- log_debug("Stored OID $k = $list->{$k}") if defined &log_debug;
|
|
|
+ log_debug("Stored OID $k = $list->{$k}");
|
|
|
$current_oid = $k;
|
|
|
}
|
|
|
last if $stop;
|
|
|
}
|
|
|
}
|
|
|
if ($session->error) {
|
|
|
- log_debug("SNMP runtime error ($host): " . $session->error) if defined &log_debug;
|
|
|
+ log_debug("SNMP runtime error ($host): " . $session->error);
|
|
|
}
|
|
|
$session->close();
|
|
|
- log_debug("SNMP walk finished on $host, total OIDs collected: " . scalar keys %table) if defined &log_debug;
|
|
|
+ log_debug("SNMP walk finished on $host, total OIDs collected: " . scalar keys %table);
|
|
|
return \%table;
|
|
|
}
|
|
|
|
|
|
#-------------------------------------------------------------------------------------
|
|
|
|
|
|
-# проверка что OID начинается с root
|
|
|
-sub oid_base_match {
|
|
|
- my ($base, $oid) = @_;
|
|
|
- return defined($oid) && defined($base) && index($oid, $base) == 0;
|
|
|
-}
|
|
|
-
|
|
|
-#-------------------------------------------------------------------------------------
|
|
|
-
|
|
|
-sub snmp_oid_compare {
|
|
|
- my ($oid1, $oid2) = @_;
|
|
|
-
|
|
|
- return 0 if !defined $oid1 && !defined $oid2;
|
|
|
- return 1 if !defined $oid2;
|
|
|
- return -1 if !defined $oid1;
|
|
|
-
|
|
|
- # Удаляем ведущую точку для единообразия
|
|
|
- $oid1 =~ s/^\.//;
|
|
|
- $oid2 =~ s/^\.//;
|
|
|
-
|
|
|
- my @a = split /\./, $oid1;
|
|
|
- my @b = split /\./, $oid2;
|
|
|
- my $len = @a < @b ? @a : @b;
|
|
|
-
|
|
|
- # Сравниваем покомпонентно как числа
|
|
|
- for (my $i = 0; $i < $len; $i++) {
|
|
|
- return -1 if $a[$i] < $b[$i];
|
|
|
- return 1 if $a[$i] > $b[$i];
|
|
|
+sub table_callback {
|
|
|
+ my ($session, $table, $root_oid, $last_oid) = @_;
|
|
|
+ my $list = $session->var_bind_list();
|
|
|
+ unless (defined $list) {
|
|
|
+ log_debug("SNMP error: " . $session->error);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ my @names = sort { snmp_oid_compare($a, $b) } $session->var_bind_names();
|
|
|
+ unless (@names) {
|
|
|
+ log_debug("No OIDs returned in this callback");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ my $next;
|
|
|
+ while (@names) {
|
|
|
+ $next = shift @names;
|
|
|
+ unless (oid_base_match($root_oid, $next)) {
|
|
|
+ log_debug("OID $next outside of root $root_oid. Exiting callback.");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (defined $last_oid && snmp_oid_compare($next, $last_oid) <= 0) {
|
|
|
+ log_debug("OID not increasing: $next <= $last_oid. Skipping.");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ my $value = $list->{$next};
|
|
|
+ # endOfMibView — конец таблицы, останавливаем
|
|
|
+ unless (defined $value) {
|
|
|
+ log_debug("endOfMibView reached at OID $next. Exiting callback.");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ $table->{$next} = $value;
|
|
|
+ log_debug("Stored OID $next = $value");
|
|
|
+ $last_oid = $next;
|
|
|
+ }
|
|
|
+ return unless defined $next;
|
|
|
+ # Запрос следующего блока — продолжаем с последнего полученного OID
|
|
|
+ my $result = $session->get_bulk_request(
|
|
|
+ -varbindlist => [$next],
|
|
|
+ -maxrepetitions => 10,
|
|
|
+ -callback => [\&table_callback, $table, $root_oid, $last_oid],
|
|
|
+ );
|
|
|
+ unless (defined $result) {
|
|
|
+ log_debug("SNMP get_bulk_request failed for OID $next: " . $session->error);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ log_debug("Scheduled next get_bulk_request starting from OID $next");
|
|
|
}
|
|
|
-
|
|
|
- # Если префиксы равны, сравниваем длину
|
|
|
- return @a <=> @b;
|
|
|
}
|
|
|
|
|
|
#-------------------------------------------------------------------------------------
|
|
|
|
|
|
sub table_callback {
|
|
|
- my ($session, $table, $root_oid, $last_oid) = @_;
|
|
|
-
|
|
|
+ my ($session, $table, $root_oid, $last_oid, $increment_failures) = @_;
|
|
|
+ $increment_failures //= 0; # ← Счётчик неудачных инкрементов
|
|
|
+
|
|
|
my $list = $session->var_bind_list();
|
|
|
-
|
|
|
unless (defined $list) {
|
|
|
log_debug("SNMP error: " . $session->error) if defined &log_debug;
|
|
|
- log_debug("Exiting callback: undefined var_bind_list") if defined &log_debug;
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
my @names = sort { snmp_oid_compare($a, $b) } $session->var_bind_names();
|
|
|
unless (@names) {
|
|
|
log_debug("No OIDs returned in this callback") if defined &log_debug;
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
+ # Нормализуем root_oid
|
|
|
+ $root_oid = _normalize_oid($root_oid);
|
|
|
+
|
|
|
my $next;
|
|
|
+ my $processed_count = 0;
|
|
|
+ my $non_increasing_count = 0;
|
|
|
+ my $batch_size = scalar(@names);
|
|
|
+
|
|
|
while (@names) {
|
|
|
$next = shift @names;
|
|
|
- # Вышли за пределы таблицы
|
|
|
+ $next = _normalize_oid($next);
|
|
|
+
|
|
|
+ # Выход за пределы таблицы
|
|
|
unless (oid_base_match($root_oid, $next)) {
|
|
|
log_debug("OID $next outside of root $root_oid. Exiting callback.") if defined &log_debug;
|
|
|
return;
|
|
|
}
|
|
|
- # Защита от OID not increasing
|
|
|
- if (defined $last_oid && snmp_oid_compare($next, $last_oid) <= 0) {
|
|
|
- log_debug("OID not increasing: $next <= $last_oid. Exiting callback.") if defined &log_debug;
|
|
|
- return;
|
|
|
- }
|
|
|
+
|
|
|
my $value = $list->{$next};
|
|
|
+
|
|
|
# endOfMibView
|
|
|
unless (defined $value) {
|
|
|
log_debug("endOfMibView reached at OID $next. Exiting callback.") if defined &log_debug;
|
|
|
return;
|
|
|
}
|
|
|
+
|
|
|
+ # ❗️ OID not increasing — пробуем инкрементировать
|
|
|
+ if (defined $last_oid && snmp_oid_compare($next, $last_oid) <= 0) {
|
|
|
+ log_debug("OID not increasing: $next <= $last_oid") if defined &log_debug;
|
|
|
+ $non_increasing_count++;
|
|
|
+
|
|
|
+ # ❗️ Инкрементируем last_oid и пропускаем этот
|
|
|
+ my $incremented = _increment_oid($last_oid);
|
|
|
+ log_debug("Incremented OID: $last_oid → $incremented") if defined &log_debug;
|
|
|
+ $next = $incremented;
|
|
|
+ $increment_failures++;
|
|
|
+
|
|
|
+ # ❗️ Защита: если слишком много инкрементов подряд — стоп
|
|
|
+ if ($increment_failures >= 10) {
|
|
|
+ log_debug("Too many increment failures ($increment_failures). Stopping to avoid loop.") if defined &log_debug;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ # Сброс счётчика если OID нормальный
|
|
|
+ $increment_failures = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ # Проверка на дубликат
|
|
|
+ if (exists $table->{$next}) {
|
|
|
+ log_debug("Duplicate OID: $next. Skipping.") if defined &log_debug;
|
|
|
+ next;
|
|
|
+ }
|
|
|
+
|
|
|
$table->{$next} = $value;
|
|
|
+ $processed_count++;
|
|
|
log_debug("Stored OID $next = $value") if defined &log_debug;
|
|
|
$last_oid = $next;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
+ # Защита от зацикливания
|
|
|
+ if ($non_increasing_count >= $batch_size && $processed_count == 0) {
|
|
|
+ log_debug("All OIDs in batch are non-increasing. Stopping to avoid loop.") if defined &log_debug;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ return unless $processed_count > 0;
|
|
|
+ return unless defined $next;
|
|
|
+
|
|
|
# Запрос следующего блока
|
|
|
my $result = $session->get_bulk_request(
|
|
|
-varbindlist => [$next],
|
|
|
-maxrepetitions => 10,
|
|
|
- -callback => [\&table_callback, $table, $root_oid, $last_oid],
|
|
|
+ -callback => [\&table_callback, $table, $root_oid, $last_oid, $increment_failures],
|
|
|
);
|
|
|
+
|
|
|
unless (defined $result) {
|
|
|
log_debug("SNMP get_bulk_request failed for OID $next: " . $session->error) if defined &log_debug;
|
|
|
}
|
|
|
@@ -655,6 +721,73 @@ sub table_callback {
|
|
|
|
|
|
#-------------------------------------------------------------------------------------
|
|
|
|
|
|
+sub _increment_oid {
|
|
|
+ my ($oid) = @_;
|
|
|
+ return undef unless defined $oid;
|
|
|
+ # Нормализуем
|
|
|
+ $oid =~ s/^\s+|\s+$//g;
|
|
|
+ $oid =~ s/^\././;
|
|
|
+ $oid =~ s/\.{2,}/./g;
|
|
|
+ # Разбиваем на компоненты
|
|
|
+ my @parts = split /\./, $oid;
|
|
|
+ # Убираем пустые элементы (от ведущей точки)
|
|
|
+ @parts = grep { length $_ > 0 } @parts;
|
|
|
+ return undef unless @parts;
|
|
|
+ # Инкрементируем последнюю компоненту
|
|
|
+ $parts[-1]++;
|
|
|
+ # Собираем обратно
|
|
|
+ return '.' . join('.', @parts);
|
|
|
+}
|
|
|
+
|
|
|
+#-------------------------------------------------------------------------------------
|
|
|
+
|
|
|
+# проверка что OID начинается с root
|
|
|
+sub oid_base_match {
|
|
|
+ my ($base, $oid) = @_;
|
|
|
+ return defined($oid) && defined($base) && index($oid, $base) == 0;
|
|
|
+}
|
|
|
+
|
|
|
+#-------------------------------------------------------------------------------------
|
|
|
+
|
|
|
+sub snmp_oid_compare {
|
|
|
+ my ($oid1, $oid2) = @_;
|
|
|
+
|
|
|
+ return 0 if !defined $oid1 && !defined $oid2;
|
|
|
+ return 1 if !defined $oid2;
|
|
|
+ return -1 if !defined $oid1;
|
|
|
+
|
|
|
+ # Удаляем ведущую точку для единообразия
|
|
|
+ $oid1 =~ s/^\.//;
|
|
|
+ $oid2 =~ s/^\.//;
|
|
|
+
|
|
|
+ my @a = split /\./, $oid1;
|
|
|
+ my @b = split /\./, $oid2;
|
|
|
+ my $len = @a < @b ? @a : @b;
|
|
|
+
|
|
|
+ # Сравниваем покомпонентно как числа
|
|
|
+ for (my $i = 0; $i < $len; $i++) {
|
|
|
+ return -1 if $a[$i] < $b[$i];
|
|
|
+ return 1 if $a[$i] > $b[$i];
|
|
|
+ }
|
|
|
+
|
|
|
+ # Если префиксы равны, сравниваем длину
|
|
|
+ return @a <=> @b;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#-------------------------------------------------------------------------------------
|
|
|
+
|
|
|
+# Функция нормализации OID
|
|
|
+sub _normalize_oid {
|
|
|
+ my ($oid) = @_;
|
|
|
+ return undef unless defined $oid;
|
|
|
+ # 1. Trim whitespace (leading/trailing)
|
|
|
+ $oid =~ s/^\s+|\s+$//g;
|
|
|
+ return $oid;
|
|
|
+}
|
|
|
+
|
|
|
+#-------------------------------------------------------------------------------------
|
|
|
+
|
|
|
sub init_snmp {
|
|
|
my ($host, $snmp, $rw, $nonblocking) = @_;
|
|
|
return unless defined $host && $host ne '';
|
|
|
@@ -668,7 +801,7 @@ sub init_snmp {
|
|
|
my %opts = (
|
|
|
-hostname => $host,
|
|
|
-port => $snmp->{port} // 161,
|
|
|
- -timeout => $snmp->{timeout} // 2,
|
|
|
+ -timeout => $snmp->{timeout} // 5,
|
|
|
-translate => [-octetstring => 0],
|
|
|
);
|
|
|
|
|
|
@@ -680,7 +813,7 @@ sub init_snmp {
|
|
|
($session, $error) = Net::SNMP->session(
|
|
|
%opts,
|
|
|
-community => $community,
|
|
|
- -version => $snmp->{version} // 2,
|
|
|
+ -version => $snmp->{version} // 5,
|
|
|
);
|
|
|
}
|
|
|
else {
|
|
|
@@ -696,7 +829,7 @@ sub init_snmp {
|
|
|
}
|
|
|
|
|
|
if (!defined $session) {
|
|
|
- log_debug("SNMP init failed for $host: $error") if defined &log_debug;
|
|
|
+ log_debug("SNMP init failed for $host: $error");
|
|
|
return;
|
|
|
}
|
|
|
|