snmp.pm 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756
  1. package eyelib::snmp;
  2. #
  3. # Copyright (C) Roman Dmitriev, rnd@rajven.ru
  4. #
  5. use utf8;
  6. use open ":encoding(utf8)";
  7. use strict;
  8. use English;
  9. use FindBin '$Bin';
  10. use lib "/opt/Eye/scripts";
  11. use base 'Exporter';
  12. use vars qw(@EXPORT @ISA);
  13. use Data::Dumper;
  14. use eyelib::config;
  15. use eyelib::main;
  16. use eyelib::logconfig;
  17. use Net::SNMP;
  18. @ISA = qw(Exporter);
  19. @EXPORT = qw(
  20. snmp_get_request
  21. snmp_set_int
  22. get_arp_table
  23. snmp_ping
  24. get_ifmib_index_table
  25. get_mac_table
  26. get_fdb_table
  27. get_vlan_at_port
  28. get_switch_vlans
  29. get_snmp_ifindex
  30. getIpAdEntIfIndex
  31. get_interfaces
  32. get_router_state
  33. snmp_get_req
  34. snmp_get_oid
  35. snmp_walk_oid
  36. oid_base_match
  37. snmp_oid_compare
  38. table_callback
  39. init_snmp
  40. setCommunity
  41. $ifAlias
  42. $ifName
  43. $ifDescr
  44. $ifIndex
  45. $ifIndex_map
  46. $ipAdEntIfIndex
  47. $arp_oid
  48. $ipNetToMediaPhysAddress
  49. $fdb_table_oid
  50. $fdb_table_oid2
  51. $dot1qPortVlanEntry
  52. $cisco_vlan_oid
  53. $sysUpTimeInstance
  54. $ifPortStatus
  55. $ifPortAdminStatus
  56. $bgp_prefixes
  57. $bgp_aslist
  58. $hrDeviceDescr
  59. $hrProcessorLoad
  60. $hrMemorySize
  61. $hrStorageIndex
  62. $hrStorageType
  63. $hrStorageDescr
  64. $hrStorageAllocationUnits
  65. $hrStorageSize
  66. $hrStorageUsed
  67. $fdb_table
  68. $snmp_timeout
  69. );
  70. BEGIN
  71. {
  72. our $ifAlias ='.1.3.6.1.2.1.31.1.1.1.18';
  73. our $ifName ='.1.3.6.1.2.1.31.1.1.1.1';
  74. our $ifDescr ='.1.3.6.1.2.1.2.2.1.2';
  75. our $ifIndex ='.1.3.6.1.2.1.2.2.1.1';
  76. our $ifIndex_map ='.1.3.6.1.2.1.17.1.4.1.2';
  77. our $ipAdEntIfIndex ='.1.3.6.1.2.1.4.20.1.2';
  78. #RFC1213::atPhysAddress
  79. our $arp_oid ='.1.3.6.1.2.1.3.1.1.2';
  80. #RFC1213::ipNetToMediaPhysAddress
  81. our $ipNetToMediaPhysAddress = '.1.3.6.1.2.1.4.22.1.2';
  82. #RFC1493::dot1dTpFdbTable
  83. our $fdb_table_oid ='.1.3.6.1.2.1.17.4.3.1.2';
  84. #Q-BRIDGE-MIB::dot1qTpFdbPort
  85. our $fdb_table_oid2='.1.3.6.1.2.1.17.7.1.2.2.1.2';
  86. #Q-BRIDGE-MIB::dot1qPortVlanEntry
  87. our $dot1qPortVlanEntry ='.1.3.6.1.2.1.17.7.1.4.5.1.1';
  88. #CISCO-ES-STACK-MIB::
  89. our $cisco_vlan_oid='.1.3.6.1.4.1.9.9.46.1.3.1.1.2';
  90. our $sysUpTimeInstance = '1.3.6.1.2.1.1.3.0';
  91. our $ifPortStatus = '1.3.6.1.2.1.2.2.1.8';
  92. our $ifPortAdminStatus = '1.3.6.1.2.1.2.2.1.7';
  93. our $bgp_prefixes = '1.3.6.1.4.1.9.9.187.1.2.4.1.1';
  94. our $bgp_aslist = '1.3.6.1.2.1.15.3.1.9';
  95. our $hrDeviceDescr = '1.3.6.1.2.1.25.3.2.1.3';
  96. our $hrProcessorLoad = '1.3.6.1.2.1.25.3.3.1.2';
  97. our $hrMemorySize = '1.3.6.1.2.1.25.2.2.0';
  98. our $hrStorageIndex = '1.3.6.1.2.1.25.2.3.1.1';
  99. our $hrStorageType = '1.3.6.1.2.1.25.2.3.1.2';
  100. our $hrStorageDescr = '1.3.6.1.2.1.25.2.3.1.3';
  101. our $hrStorageAllocationUnits = '1.3.6.1.2.1.25.2.3.1.4';
  102. our $hrStorageSize = '1.3.6.1.2.1.25.2.3.1.5';
  103. our $hrStorageUsed = '1.3.6.1.2.1.25.2.3.1.6';
  104. our $snmp_timeout = 15;
  105. #---------------------------------------------------------------------------------
  106. sub snmp_get_request {
  107. my $ip = shift;
  108. my $oid = shift;
  109. my $snmp = shift;
  110. my $session = init_snmp ($ip,$snmp);
  111. return if (!defined($session) or !$session);
  112. my $result = $session->get_request( -varbindlist => [$oid]);
  113. $session->close;
  114. return if (!$result->{$oid});
  115. return $result->{$oid};
  116. }
  117. #---------------------------------------------------------------------------------
  118. sub snmp_set_int {
  119. my $ip = shift;
  120. my $oid = shift;
  121. my $value = shift;
  122. my $snmp = shift;
  123. my $session = init_snmp ($ip,$snmp,1);
  124. return if (!defined($session) or !$session);
  125. my $result = $session->set_request( -varbindlist => [$oid,INTEGER,$value]);
  126. $session->close;
  127. return $result->{$oid};
  128. }
  129. #-------------------------------------------------------------------------------------
  130. sub get_arp_table {
  131. my ($host,$snmp) = @_;
  132. my $session = init_snmp ($host,$snmp,0);
  133. return if (!defined($session) or !$session);
  134. $session->translate([-all]);
  135. my $arp;
  136. my $arp_table1 = $session->get_table($arp_oid);
  137. my $arp_table2 = $session->get_table($ipNetToMediaPhysAddress);
  138. $session->close();
  139. if ($arp_table1) {
  140. foreach my $row (keys(%$arp_table1)) {
  141. my ($mac_h) = unpack("H*",$arp_table1->{$row});
  142. next if (!$mac_h or $mac_h eq '000000000000' or $mac_h eq 'ffffffffffff');
  143. my $mac;
  144. if (length($mac_h)==12) { $mac=lc $mac_h; }
  145. next if (!$mac);
  146. $row=trim($row);
  147. my $ip;
  148. if ($row=~/\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/) { $ip=$1.".".$2.".".$3.".".$4; }
  149. next if (!$ip);
  150. $arp->{$ip}=$mac;
  151. };
  152. }
  153. if ($arp_table2) {
  154. foreach my $row (keys(%$arp_table2)) {
  155. my ($mac_h) = unpack("H*",$arp_table2->{$row});
  156. next if (!$mac_h or $mac_h eq '000000000000' or $mac_h eq 'ffffffffffff');
  157. my $mac;
  158. if (length($mac_h)==12) { $mac=lc $mac_h; }
  159. next if (!$mac);
  160. $row=trim($row);
  161. my $ip;
  162. if ($row=~/\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/) { $ip=$1.".".$2.".".$3.".".$4; }
  163. next if (!$ip);
  164. $arp->{$ip}=$mac;
  165. };
  166. }
  167. return $arp;
  168. }
  169. #---------------------------------------------------------------------------------
  170. sub snmp_ping {
  171. my ($host, $snmp) = @_;
  172. my @test_oids = (
  173. '.1.3.6.1.2.1.1.1.0', # sysDescr (model)
  174. '.1.3.6.1.2.1.1.3.0', # sysUpTime (uptime)
  175. '.1.3.6.1.2.1.1.5.0', # sysName (hostname)
  176. );
  177. my $result;
  178. my $old_sig_alarm = $SIG{ALRM};
  179. my $fast_snmp = $snmp;
  180. $fast_snmp->{timeout}=10;
  181. $SIG{ALRM} = sub { die "Timeout ${WAIT_TIME}s reached.\n" };
  182. alarm($WAIT_TIME // 11);
  183. eval {
  184. foreach my $oid (@test_oids) {
  185. log_debug("SNMP ping: trying $oid on $host");
  186. $result = snmp_get_request($host, $oid, $fast_snmp);
  187. if (defined $result) {
  188. log_debug("SNMP ping: SUCCESS $oid = '$result' on $host");
  189. last;
  190. }
  191. log_debug("SNMP ping: failed $oid on $host");
  192. }
  193. };
  194. my $eval_error = $@;
  195. alarm(0);
  196. $SIG{ALRM} = $old_sig_alarm;
  197. my $success = (defined $result && !$eval_error) ? 1 : 0;
  198. if ($success) {
  199. log_debug("SNMP ping: $host is UP");
  200. } else {
  201. my $reason = $eval_error ? "timeout/error: $eval_error" : "no OID responded";
  202. log_warning("SNMP ping: $host is DOWN ($reason)");
  203. }
  204. return $success;
  205. }
  206. #-------------------------------------------------------------------------------------
  207. sub get_ifmib_index_table {
  208. my $ip = shift;
  209. my $snmp = shift;
  210. my $ifmib_map;
  211. my $is_mikrotik = snmp_get_request($ip, '.1.3.6.1.2.1.9999.1.1.1.1.0', $snmp);
  212. my $mk_ros_version = 0;
  213. if ($is_mikrotik=~/MikroTik/i) {
  214. my $mikrotik_version = snmp_get_request($ip, '.1.0.8802.1.1.2.1.3.4.0', $snmp);
  215. $mk_ros_version = 6491;
  216. #"MikroTik RouterOS 6.46.8 (long-term) CRS326-24S+2Q+"
  217. if ($mikrotik_version =~/RouterOS\s+(\d)\.(\d{1,3})\.(\d{1,3})\s+/) {
  218. $mk_ros_version = $1*1000 + $2*10 + $3;
  219. }
  220. }
  221. if (!$mk_ros_version or $mk_ros_version > 6468) {
  222. my $index_map_table = snmp_get_oid($ip, $snmp, $ifIndex_map);
  223. if (!$index_map_table) { $index_map_table = snmp_walk_oid($ip, $snmp, $ifIndex_map); }
  224. if ($index_map_table) {
  225. foreach my $row (keys(%$index_map_table)) {
  226. my $port_index = $index_map_table->{$row};
  227. next if (!$port_index);
  228. my $value;
  229. if ($row=~/\.([0-9]{1,10})$/) { $value = $1; }
  230. next if (!$value);
  231. $ifmib_map->{$value}=$port_index;
  232. }
  233. }
  234. }
  235. if (!$ifmib_map) {
  236. my $index_table = snmp_get_oid($ip, $snmp, $ifIndex);
  237. if (!$index_table) { $index_table = snmp_walk_oid($ip, $snmp, $ifIndex); }
  238. foreach my $row (keys(%$index_table)) {
  239. my $port_index = $index_table->{$row};
  240. next if (!$port_index);
  241. my $value;
  242. if ($row=~/\.([0-9]{1,10})$/) { $value = $1; }
  243. next if (!$value);
  244. $ifmib_map->{$value}=$value;
  245. };
  246. }
  247. return $ifmib_map;
  248. }
  249. #-------------------------------------------------------------------------------------
  250. sub get_mac_table {
  251. my ($host,$snmp,$oid,$index_map) = @_;
  252. my $fdb;
  253. my $fdb_table1 = snmp_get_oid($host,$snmp,$oid);
  254. if (!$fdb_table1) { $fdb_table1=snmp_walk_oid($host,$snmp,$oid,undef); }
  255. if ($fdb_table1) {
  256. foreach my $row (keys(%$fdb_table1)) {
  257. my $port_index = $fdb_table1->{$row};
  258. next if (!$port_index);
  259. my $mac;
  260. 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})$/) {
  261. $mac=sprintf "%02x%02x%02x%02x%02x%02x",$1,$2,$3,$4,$5,$6;
  262. }
  263. next if (!$mac);
  264. if ($index_map and exists $index_map->{$port_index}) { $port_index = $index_map->{$port_index}; }
  265. $fdb->{$mac}=$port_index;
  266. };
  267. return $fdb;
  268. }
  269. }
  270. #-------------------------------------------------------------------------------------
  271. sub get_fdb_table {
  272. my ($host,$snmp) = @_;
  273. my $ifindex_map = get_ifmib_index_table($host,$snmp);
  274. my $fdb1=get_mac_table($host,$snmp,$fdb_table_oid,$ifindex_map);
  275. my $fdb2=get_mac_table($host,$snmp,$fdb_table_oid2,$ifindex_map);
  276. my $fdb;
  277. #join tables
  278. if (!$fdb1 and $fdb2) { $fdb = $fdb2; }
  279. if (!$fdb2 and $fdb1) { $fdb = $fdb1; }
  280. if ($fdb1 and $fdb2) { $fdb = { %$fdb1,%$fdb2 }; }
  281. my $snmp_cisco = $snmp;
  282. #maybe cisco?!
  283. if (!$fdb) {
  284. my $vlan_table=snmp_get_oid($host,$snmp,$cisco_vlan_oid);
  285. if (!$vlan_table) { $vlan_table=snmp_walk_oid($host,$snmp,$cisco_vlan_oid); }
  286. # just empty
  287. if (!$vlan_table) { return; }
  288. #fucking cisco!
  289. my %fdb_vlan;
  290. foreach my $vlan_oid (keys %$vlan_table) {
  291. next if (!$vlan_oid);
  292. my $vlan_id;
  293. if ($vlan_oid=~/\.([0-9]{1,4})$/) { $vlan_id=$1; }
  294. next if (!$vlan_id);
  295. next if ($vlan_id>1000 and $vlan_id<=1009);
  296. $snmp_cisco->{'ro-community'} = $snmp->{'ro-community'}.'@'.$vlan_id;
  297. $fdb_vlan{$vlan_id}=get_mac_table($host,$snmp_cisco,$fdb_table_oid,$ifindex_map);
  298. if (!$fdb_vlan{$vlan_id}) { $fdb_vlan{$vlan_id}=get_mac_table($host,$snmp_cisco,$fdb_table_oid2,$ifindex_map); }
  299. }
  300. foreach my $vlan_id (keys %fdb_vlan) {
  301. next if (!exists $fdb_vlan{$vlan_id});
  302. if (defined $fdb_vlan{$vlan_id}) {
  303. my %tmp=%{$fdb_vlan{$vlan_id}};
  304. foreach my $mac (keys %tmp) {
  305. next if (!$mac);
  306. $fdb->{$mac}=$tmp{$mac};
  307. }
  308. }
  309. }
  310. }
  311. return $fdb;
  312. }
  313. #-------------------------------------------------------------------------------------
  314. sub get_vlan_at_port {
  315. my ($host,$snmp,$port_index) = @_;
  316. my $vlan_oid=$dot1qPortVlanEntry.".".$port_index;
  317. my $vlan = snmp_get_req($host,$snmp,$vlan_oid);
  318. return "1" if (!$vlan);
  319. return "1" if ($vlan=~/noSuchObject/i);
  320. return "1" if ($vlan=~/noSuchInstance/i);
  321. return $vlan;
  322. }
  323. #-------------------------------------------------------------------------------------
  324. sub get_switch_vlans {
  325. my ($host,$snmp) = @_;
  326. my $result;
  327. #need for callback
  328. my $vlan_table = snmp_get_oid($host,$snmp,$dot1qPortVlanEntry);
  329. if (!$vlan_table) { $vlan_table=snmp_walk_oid($host,$snmp,$dot1qPortVlanEntry); }
  330. if ($vlan_table) {
  331. foreach my $vlan_oid (keys %$vlan_table) {
  332. if ($vlan_oid=~/\.([0-9]*)$/) { $result->{$1} = $vlan_table->{$vlan_oid}; }
  333. }
  334. }
  335. return $result;
  336. }
  337. #-------------------------------------------------------------------------------------
  338. sub get_snmp_ifindex {
  339. my ($host,$snmp) = @_;
  340. my $session = init_snmp($host,$snmp,0);
  341. return if (!defined($session) or !$session);
  342. my $if_index = $session->get_table($ifIndex);
  343. my $result;
  344. foreach my $row (keys(%$if_index)) {
  345. my $value = $if_index->{$row};
  346. $row=~s/^$ifIndex\.//;
  347. $result->{$row}=$value;
  348. };
  349. $session->close();
  350. return $result;
  351. }
  352. #-------------------------------------------------------------------------------------
  353. #get ip interfaces
  354. sub getIpAdEntIfIndex {
  355. my ($host,$snmp) = @_;
  356. my $session = init_snmp ($host,$snmp,0);
  357. return if (!defined($session) or !$session);
  358. $session->translate([-timeticks]);
  359. my $if_ipaddr = $session->get_table($ipAdEntIfIndex);
  360. my $l3_list;
  361. foreach my $row (keys(%$if_ipaddr)) {
  362. my $ipaddr = $row;
  363. $ipaddr=~s/$ipAdEntIfIndex\.//;
  364. $l3_list->{$ipaddr}=$if_ipaddr->{$row};
  365. }
  366. $session->close();
  367. return $l3_list;
  368. }
  369. #-------------------------------------------------------------------------------------
  370. sub get_interfaces {
  371. my ($host,$snmp,$skip_empty) = @_;
  372. my $session = init_snmp ($host,$snmp,0);
  373. return if (!defined($session) or !$session);
  374. $session->translate([-timeticks]);
  375. my $if_name = $session->get_table($ifName);
  376. my $if_alias = $session->get_table($ifAlias);
  377. my $if_descr = $session->get_table($ifDescr);
  378. my $if_index = $session->get_table($ifIndex);
  379. $session->close();
  380. my $dev_cap;
  381. foreach my $row (keys(%$if_index)) {
  382. my $index = $if_index->{$row};
  383. next if ($if_name->{$ifName.".".$index} =~/^lo/i);
  384. next if ($if_name->{$ifName.".".$index} =~/^dummy/i);
  385. next if ($if_name->{$ifName.".".$index} =~/^enet/i);
  386. next if ($if_name->{$ifName.".".$index} =~/^Nu/i);
  387. # next if ($if_name->{$ifName.".".$index} =~/^Po/i);
  388. my $ifc_alias;
  389. $ifc_alias=$if_alias->{$ifAlias.".".$index} if ($if_alias->{$ifAlias.".".$index});
  390. my $ifc_name;
  391. $ifc_name=$if_name->{$ifName.".".$index} if ($if_name->{$ifName.".".$index});
  392. my $ifc_desc;
  393. $ifc_desc=$if_descr->{$ifDescr.".".$index} if ($if_descr->{$ifDescr.".".$index});
  394. $dev_cap->{$index}->{alias}=$ifc_alias if ($ifc_alias);
  395. $dev_cap->{$index}->{name}=$ifc_name if ($ifc_name);
  396. $dev_cap->{$index}->{desc}=$ifc_desc if ($ifc_desc);
  397. $dev_cap->{$index}->{index} = $index;
  398. };
  399. return $dev_cap;
  400. }
  401. #-------------------------------------------------------------------------------------
  402. sub get_router_state {
  403. my ($host,$snmp,$skip_empty) = @_;
  404. my $session = init_snmp ($host,$snmp,0);
  405. return if (!defined($session) or !$session);
  406. $session->translate([-timeticks]);
  407. my $router_status = $session->get_table("1.3.6.1.4.1.10.1");
  408. $session->close();
  409. return ($router_status);
  410. }
  411. #-------------------------------------------------------------------------------------
  412. sub snmp_get_req {
  413. my ($host,$snmp,$oid) = @_;
  414. my $session = init_snmp ($host,$snmp,0);
  415. return if (!defined($session) or !$session);
  416. $session->translate([-timeticks]);
  417. my $result = $session->get_request(-varbindlist => [$oid]) or return;
  418. $session->close();
  419. return $result->{$oid};
  420. }
  421. #-------------------------------------------------------------------------------------
  422. sub snmp_get_oid {
  423. my ($host,$snmp,$oid) = @_;
  424. my $port = 161;
  425. my $session = init_snmp ($host,$snmp,0);
  426. return if (!defined($session) or !$session);
  427. $session->translate([-timeticks]);
  428. my $table = $session->get_table($oid);
  429. $session->close();
  430. return $table;
  431. }
  432. #-------------------------------------------------------------------------------------
  433. sub snmp_walk_oid {
  434. my ($host, $snmp, $oid, $opt) = @_;
  435. $opt ||= {};
  436. my $nonblocking = $opt->{nonblocking} // 1;
  437. log_debug("Starting SNMP walk on $host, OID: $oid, nonblocking=" . ($nonblocking ? 1 : 0));
  438. my $session = init_snmp($host, $snmp, 'ro', $nonblocking);
  439. unless ($session) {
  440. log_debug("Failed to initialize SNMP session for $host");
  441. return;
  442. }
  443. my %table;
  444. if ($nonblocking) {
  445. # Async walk через callback
  446. log_debug("Sending first get_bulk_request for OID $oid");
  447. my $result = $session->get_bulk_request(
  448. -varbindlist => [$oid],
  449. -callback => [\&table_callback, \%table, $oid, undef],
  450. -maxrepetitions => 10,
  451. );
  452. unless (defined $result) {
  453. log_debug("SNMP request error ($host): " . $session->error);
  454. $session->close();
  455. return;
  456. }
  457. # Запускаем dispatcher для обработки async запросов
  458. eval {
  459. log_debug("Starting snmp_dispatcher for $host");
  460. snmp_dispatcher();
  461. };
  462. if ($@) {
  463. log_debug("SNMP dispatcher exception ($host): $@");
  464. $session->close();
  465. return;
  466. }
  467. }
  468. else {
  469. # Blocking walk через get_next
  470. log_debug("Starting blocking SNMP walk for OID $oid");
  471. my $current_oid = $oid;
  472. while (1) {
  473. my $result = $session->get_next_request(-varbindlist => [$current_oid]);
  474. unless (defined $result) {
  475. log_debug("SNMP request error ($host): " . $session->error);
  476. last;
  477. }
  478. my $list = $session->var_bind_list();
  479. last unless defined $list;
  480. my $stop = 0;
  481. for my $k (keys %$list) {
  482. unless (oid_base_match($oid, $k)) {
  483. log_debug("OID $k outside root $oid, stopping walk");
  484. $stop = 1;
  485. last;
  486. }
  487. $table{$k} = $list->{$k};
  488. log_debug("Stored OID $k = $list->{$k}");
  489. $current_oid = $k;
  490. }
  491. last if $stop;
  492. }
  493. }
  494. if ($session->error) {
  495. log_debug("SNMP runtime error ($host): " . $session->error);
  496. }
  497. $session->close();
  498. log_debug("SNMP walk finished on $host, total OIDs collected: " . scalar keys %table);
  499. return \%table;
  500. }
  501. #-------------------------------------------------------------------------------------
  502. sub table_callback {
  503. my ($session, $table, $root_oid, $last_oid) = @_;
  504. my $list = $session->var_bind_list();
  505. unless (defined $list) {
  506. log_debug("SNMP error: " . $session->error);
  507. return;
  508. }
  509. my @names = $session->var_bind_names();
  510. unless (@names) {
  511. log_debug("No OIDs returned in this callback");
  512. return;
  513. }
  514. $root_oid = _normalize_oid($root_oid);
  515. my $next;
  516. my $processed_count = 0;
  517. my $seen_in_batch = {}; # ← Дубликаты ВНУТРИ одного ответа
  518. while (@names) {
  519. $next = shift @names;
  520. $next = _normalize_oid($next);
  521. # Выход за пределы таблицы
  522. unless (oid_base_match($root_oid, $next)) {
  523. log_debug("OID $next outside of root $root_oid. Exiting.");
  524. return;
  525. }
  526. my $value = $list->{$next};
  527. unless (defined $value) {
  528. log_debug("endOfMibView at $next. Exiting.");
  529. return;
  530. }
  531. # Пропускаем дубликаты ВНУТРИ этого пакета
  532. if ($seen_in_batch->{$next}) {
  533. log_debug("Duplicate in batch: $next. Skipping.");
  534. next;
  535. }
  536. $seen_in_batch->{$next} = 1;
  537. # Пропускаем если УЖЕ есть в таблице (из предыдущих пакетов)
  538. if (exists $table->{$next}) {
  539. log_debug("Already in table: $next. Skipping.");
  540. next;
  541. }
  542. # Сохраняем
  543. $table->{$next} = $value;
  544. $processed_count++;
  545. log_debug("Stored OID $next = $value");
  546. # Обновляем last_oid для следующего запроса (максимальный из обработанных)
  547. if (!defined $last_oid || snmp_oid_compare($next, $last_oid) > 0) {
  548. $last_oid = $next;
  549. }
  550. }
  551. return unless $processed_count > 0;
  552. return unless defined $next;
  553. # Следующий запрос — от последнего "максимального" OID
  554. my $result = $session->get_bulk_request(
  555. -varbindlist => [$last_oid],
  556. -maxrepetitions => 10,
  557. -callback => [\&table_callback, $table, $root_oid, $last_oid],
  558. );
  559. unless (defined $result) {
  560. log_debug("get_bulk_request failed: " . $session->error);
  561. }
  562. }
  563. #-------------------------------------------------------------------------------------
  564. # проверка что OID начинается с root
  565. sub oid_base_match {
  566. my ($base, $oid) = @_;
  567. return defined($oid) && defined($base) && index($oid, $base) == 0;
  568. }
  569. #-------------------------------------------------------------------------------------
  570. sub snmp_oid_compare {
  571. my ($oid1, $oid2) = @_;
  572. return 0 if !defined $oid1 && !defined $oid2;
  573. return 1 if !defined $oid2;
  574. return -1 if !defined $oid1;
  575. # Удаляем ведущую точку для единообразия
  576. $oid1 =~ s/^\.//;
  577. $oid2 =~ s/^\.//;
  578. my @a = split /\./, $oid1;
  579. my @b = split /\./, $oid2;
  580. my $len = @a < @b ? @a : @b;
  581. # Сравниваем покомпонентно как числа
  582. for (my $i = 0; $i < $len; $i++) {
  583. return -1 if $a[$i] < $b[$i];
  584. return 1 if $a[$i] > $b[$i];
  585. }
  586. # Если префиксы равны, сравниваем длину
  587. return @a <=> @b;
  588. }
  589. #-------------------------------------------------------------------------------------
  590. # Функция нормализации OID
  591. sub _normalize_oid {
  592. my ($oid) = @_;
  593. return undef unless defined $oid;
  594. # 1. Trim whitespace (leading/trailing)
  595. $oid =~ s/^\s+|\s+$//g;
  596. return $oid;
  597. }
  598. #-------------------------------------------------------------------------------------
  599. sub init_snmp {
  600. my ($host, $snmp, $rw, $nonblocking) = @_;
  601. return unless defined $host && $host ne '';
  602. $rw ||= 'ro';
  603. my $community = ($rw eq 'rw')
  604. ? $snmp->{'rw-community'}
  605. : $snmp->{'ro-community'};
  606. my %opts = (
  607. -hostname => $host,
  608. -port => $snmp->{port} // 161,
  609. -timeout => $snmp->{timeout} // 5,
  610. -translate => [-octetstring => 0],
  611. );
  612. $opts{-nonblocking} = 1 if $nonblocking;
  613. my ($session, $error);
  614. if (($snmp->{version} // 2) <= 2) {
  615. ($session, $error) = Net::SNMP->session(
  616. %opts,
  617. -community => $community,
  618. -version => $snmp->{version} // 5,
  619. );
  620. }
  621. else {
  622. ($session, $error) = Net::SNMP->session(
  623. %opts,
  624. -version => 'snmpv3',
  625. -username => $snmp->{$rw . '-user'},
  626. -authprotocol => $snmp->{'auth-proto'},
  627. -privprotocol => $snmp->{'priv-proto'},
  628. -authpassword => $snmp->{$rw . '-password'},
  629. -privpassword => $snmp->{$rw . '-password'},
  630. );
  631. }
  632. if (!defined $session) {
  633. log_debug("SNMP init failed for $host: $error");
  634. return;
  635. }
  636. return $session;
  637. }
  638. #-------------------------------------------------------------------------------------
  639. sub setCommunity {
  640. my $device = shift;
  641. $device->{snmp}->{'port'} = 161;
  642. $device->{snmp}->{'timeout'} = $snmp_timeout;
  643. $device->{snmp}->{'version'} = $device->{snmp_version} || '2';
  644. $device->{snmp}->{'ro-community'} = $device->{community} || $snmp_default_community;
  645. $device->{snmp}->{'rw-community'} = $device->{rw_community} || $snmp_default_community;
  646. #snmpv3
  647. $device->{snmp}->{'auth-proto'} = $device->{snmp3_auth_proto} || 'sha512';
  648. $device->{snmp}->{'priv-proto'} = $device->{snmp3_priv_proto} || 'aes128';
  649. $device->{snmp}->{'ro-user'} = $device->{snmp3_user_ro} || '';
  650. $device->{snmp}->{'rw-user'} = $device->{snmp3_user_rw} || '';
  651. $device->{snmp}->{'ro-password'} = $device->{snmp3_user_ro_password} || $snmp_default_community;
  652. $device->{snmp}->{'rw-password'} = $device->{snmp3_user_rw_password} || $snmp_default_community;
  653. }
  654. #-------------------------------------------------------------------------------------
  655. 1;
  656. }