snmp.pm 25 KB

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