Преглед изворни кода

fixed the installation of locks for polling equipment
added a bypass for the "OID not increasing" error

root пре 3 недеља
родитељ
комит
7a012f029d
3 измењених фајлова са 267 додато и 126 уклоњено
  1. 45 35
      scripts/eyelib/common.pm
  2. 210 77
      scripts/eyelib/snmp.pm
  3. 12 14
      scripts/fetch_new_arp.pl

+ 45 - 35
scripts/eyelib/common.pm

@@ -829,57 +829,64 @@ if ($fqdn ne '' and !$dynamic_ok) {
 
 #------------------------------------------------------------------------------------------------------------
 
-
 sub apply_device_lock {
     my $db = shift;
     my $device_id = shift;
     my $iteration = shift || 0;
     $iteration++;
-    if ($iteration > 2) { return 0; }
-
-    my $dev = get_record_sql($db, "SELECT discovery_locked, locked_timestamp FROM devices WHERE id = ?", $device_id);
-
-    if (!$dev) { return 0; }
-
+    log_debug("apply_device_lock: device=$device_id, iteration=$iteration");
+    if ($iteration > 2) {
+        log_debug("device_lock: max iterations reached");
+        return 0;
+    }
+    my $dev = get_record_sql($db, 
+        "SELECT discovery_locked, locked_timestamp FROM devices WHERE id = ?",
+        $device_id
+    );
+    if (!$dev) {
+        log_debug("device_lock: device $device_id not found in DB");
+        return 0;
+    }
+    log_debug("device_lock: current state: locked=$dev->{discovery_locked}, ts=$dev->{locked_timestamp}");
+    # Если не заблокировано — ставим лок
     if (!$dev->{'discovery_locked'}) {
-        return set_lock_discovery($db, $device_id);
+        log_debug("device_lock: device not locked, acquiring");
+        my $res = set_lock_discovery($db, $device_id);
+        log_debug("device_lock: set_lock_discovery returned=$res");
+        return $res;
     }
-
     my $ts_str = $dev->{'locked_timestamp'};
-
-    # Если locked_timestamp NULL или пустой — устанавливаем блокировку
+    # Пустой таймстамп
     if (!defined $ts_str || $ts_str eq '' || $ts_str eq '0000-00-00 00:00:00') {
+        log_debug("device_lock: empty timestamp, reacquiring");
         return set_lock_discovery($db, $device_id);
     }
-
-    # Удаляем микросекунды (PostgreSQL) для совместимости с форматом
+    # Парсинг
     $ts_str =~ s/\.\d+$//;
-
-    # Парсим строку в DateTime
     my $parser = DateTime::Format::Strptime->new(
-        pattern   => '%Y-%m-%d %H:%M:%S',
-        on_error  => 'croak',
+        pattern => '%Y-%m-%d %H:%M:%S',
+        time_zone => 'local',
+        on_error => 'croak',
     );
-
     my $dt;
-    eval {
-        $dt = $parser->parse_datetime($ts_str);
-    };
-
+    eval { $dt = $parser->parse_datetime($ts_str); };
     if ($@ || !$dt) {
-        # Ошибка парсинга — считаем блокировку недействительной
+        log_debug("device_lock: parse failed for '$ts_str': $@");
         return set_lock_discovery($db, $device_id);
     }
-
-    # Получаем Unix timestamp
     my $u_locked_timestamp = $dt->epoch;
-
-    # Ждём окончания блокировки (30 секунд)
-    my $wait_time = ($u_locked_timestamp + 30) - time();
+    my $now = time();
+    my $wait_time = ($u_locked_timestamp + 30) - $now;
+    log_debug("device_lock: locked_epoch=$u_locked_timestamp, now=$now, wait_time=$wait_time");
+    # Лок просрочен
     if ($wait_time <= 0) {
-        return set_lock_discovery($db, $device_id);
+        log_debug("device_lock: lock EXPIRED, attempting to reacquire");
+        my $res = set_lock_discovery($db, $device_id);
+        log_debug("apply_device_lock: reacquire result=$res");
+        return $res;
     }
-
+    # Ждём
+    log_debug("device_lock: sleeping $wait_time seconds");
     sleep($wait_time);
     return apply_device_lock($db, $device_id, $iteration);
 }
@@ -889,11 +896,14 @@ sub apply_device_lock {
 sub set_lock_discovery {
     my $db = shift;
     my $device_id = shift;
+    my $now = GetNowTime();
+    log_debug("set_lock_discovery: device=$device_id, timestamp=$now");
     my $new;
     $new->{'discovery_locked'} = 1;
-    $new->{'locked_timestamp'} = GetNowTime();
-    if (update_record($db,'devices',$new,'id=?', $device_id)) { return 1; }
-    return 0;
+    $new->{'locked_timestamp'} = $now;
+    my $result = update_record($db, 'devices', $new, 'id=?', $device_id);
+    log_debug("set_lock_discovery: update_record returned=$result");
+    return $result ? 1 : 0;
 }
 
 #------------------------------------------------------------------------------------------------------------
@@ -904,8 +914,8 @@ sub unset_lock_discovery {
     my $new;
     $new->{'discovery_locked'} = 0;
     $new->{'locked_timestamp'} = GetNowTime();
-    if (update_record($db,'devices',$new,'id=?',$device_id)) { return 1; }
-    return 0;
+    update_record($db,'devices',$new,'id=?',$device_id);
+    return 1;
 }
 
 #------------------------------------------------------------------------------------------------------------

+ 210 - 77
scripts/eyelib/snmp.pm

@@ -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;
     }
 

+ 12 - 14
scripts/fetch_new_arp.pl

@@ -147,17 +147,17 @@ DATA_LOOP:
 foreach my $router (@router_ref) {
     my $router_ip = $router->{ip};
     setCommunity($router);
-    if (!HostIsLive($router_ip)) {
-        log_info("Host id: $router->{id} name: $router->{device_name} ip: $router_ip is down! Skip.");
+    if (!(HostIsLive($router_ip) && snmp_ping($router_ip, $router->{snmp}))) {
+        log_info("Host id: $router->{id} name: $router->{device_name} ip: $router_ip is down or not snmp access! Skip.");
         next;
     }
     $pm_arp->start() and next DATA_LOOP;
 
     my $arp_table;
     my $tmp_dbh = init_db();
-    if (apply_device_lock($tmp_dbh, $router->{id})) {
+    if (apply_device_lock($tmp_dbh, $router->{id},0)) {
         $arp_table = get_arp_table($router_ip, $router->{snmp});
-        log_debug("ARP TABLE at router: $router_ip".Dumper($arp_table));
+        log_debug("ARP table for $router_ip: ".Dumper($arp_table));
         unset_lock_discovery($tmp_dbh, $router->{id});
     }
     $tmp_dbh->disconnect;
@@ -186,6 +186,8 @@ foreach my $row (@authlist_ref) {
     $ip_list{$row->{id}}->{mac}  = mac_splitted($row->{mac}) || '';
 }
 
+log_debug("Found ARP tables: ".Dumper(\@arp_array));
+
 # Process all collected ARP tables
 foreach my $arp_table (@arp_array) {
     foreach my $ip (keys %$arp_table) {
@@ -284,14 +286,8 @@ $dbh->disconnect;
 FDB_LOOP:
 foreach my $device (@device_list) {
     setCommunity($device);
-    if (!HostIsLive($device->{ip})) {
-        log_info("Host id: $device->{id} name: $device->{device_name} ip: $device->{ip} is down! Skip.");
-        next;
-    }
-
-    my $int_list = get_snmp_ifindex($device->{ip}, $device->{snmp});
-    if (!$int_list) {
-        log_info("Host id: $device->{id} name: $device->{device_name} ip: $device->{ip} interfaces not found by SNMP request! Skip.");
+    if (!(HostIsLive($device->{ip}) && snmp_ping($device->{ip}, $device->{snmp}))) {
+        log_info("Host id: $device->{id} name: $device->{device_name} ip: $device->{ip} is down or not snmp access! Skip.");
         next;
     }
 
@@ -299,10 +295,10 @@ foreach my $device (@device_list) {
 
     my $result;
     my $tmp_dbh = init_db();
-    if (apply_device_lock($tmp_dbh, $device->{id})) {
+    if (apply_device_lock($tmp_dbh, $device->{id},0)) {
         my $fdb = get_fdb_table($device->{ip}, $device->{snmp});
+        log_debug("MAC table for device $device->{ip}: ".Dumper($fdb));
         unset_lock_discovery($tmp_dbh, $device->{id});
-        log_debug("MAC TABLE at device: $device->{ip}".Dumper($fdb));
         $result->{id} = $device->{id};
         $result->{fdb} = $fdb;
     }
@@ -506,5 +502,7 @@ foreach my $mac (keys %mac_history) {
     insert_record($dbh, 'mac_history', $history_rec);
 }
 
+log_info("Work finished.");
+
 $dbh->disconnect;
 exit 0;