SNMP_Session.pm
上传用户:shbosideng
上传日期:2013-05-04
资源大小:1555k
文件大小:33k
源码类别:

SNMP编程

开发平台:

C/C++

  1. ### -*- mode: Perl -*-
  2. ######################################################################
  3. ### SNMP Request/Response Handling
  4. ######################################################################
  5. ### Copyright (c) 1995-2002, Simon Leinen.
  6. ###
  7. ### This program is free software; you can redistribute it under the
  8. ### "Artistic License" included in this distribution (file "Artistic").
  9. ######################################################################
  10. ### The abstract class SNMP_Session defines objects that can be used
  11. ### to communicate with SNMP entities.  It has methods to send
  12. ### requests to and receive responses from an agent.
  13. ###
  14. ### Two instantiable subclasses are defined:
  15. ### SNMPv1_Session implements SNMPv1 (RFC 1157) functionality
  16. ### SNMPv2c_Session implements community-based SNMPv2.
  17. ######################################################################
  18. ### Created by:  Simon Leinen  <simon@switch.ch>
  19. ###
  20. ### Contributions and fixes by:
  21. ###
  22. ### Matthew Trunnell <matter@media.mit.edu>
  23. ### Tobias Oetiker <oetiker@ee.ethz.ch>
  24. ### Heine Peters <peters@dkrz.de>
  25. ### Daniel L. Needles <dan_needles@INS.COM>
  26. ### Mike Mitchell <mcm@unx.sas.com>
  27. ### Clinton Wong <clintdw@netcom.com>
  28. ### Alan Nichols <Alan.Nichols@Ebay.Sun.COM>
  29. ### Mike McCauley <mikem@open.com.au>
  30. ### Andrew W. Elble <elble@icculus.nsg.nwu.edu>
  31. ### Brett T Warden <wardenb@eluminant.com>: pretty UInteger32
  32. ### Michael Deegan <michael@cnspc18.murdoch.edu.au>
  33. ### Sergio Macedo <macedo@tmp.com.br>
  34. ### Jakob Ilves (/IlvJa) <jakob.ilves@oracle.com>: PDU capture
  35. ### Valerio Bontempi <v.bontempi@inwind.it>: IPv6 support
  36. ### Lorenzo Colitti <lorenzo@colitti.com>: IPv6 support
  37. ### Philippe Simonet <Philippe.Simonet@swisscom.com>: Export avoid...
  38. ### Luc Pauwels <Luc.Pauwels@xalasys.com>: use_16bit_request_ids
  39. ######################################################################
  40. package SNMP_Session;
  41. require 5.002;
  42. use strict;
  43. use Exporter;
  44. use vars qw(@ISA $VERSION @EXPORT $errmsg
  45.     $suppress_warnings
  46.     $default_avoid_negative_request_ids
  47.     $default_use_16bit_request_ids);
  48. use Socket;
  49. use BER '1.05';
  50. use Carp;
  51. sub map_table ($$$ );
  52. sub map_table_4 ($$$$);
  53. sub map_table_start_end ($$$$$$);
  54. sub index_compare ($$);
  55. sub oid_diff ($$);
  56. $VERSION = '1.05';
  57. @ISA = qw(Exporter);
  58. @EXPORT = qw(errmsg suppress_warnings index_compare oid_diff recycle_socket ipv6available);
  59. my $default_debug = 0;
  60. ### Default initial timeout (in seconds) waiting for a response PDU
  61. ### after a request is sent.  Note that when a request is retried, the
  62. ### timeout is increased by BACKOFF (see below).
  63. ###
  64. my $default_timeout = 2.0;
  65. ### Default number of attempts to get a reply for an SNMP request.  If
  66. ### no response is received after TIMEOUT seconds, the request is
  67. ### resent and a new response awaited with a longer timeout (see the
  68. ### documentation on BACKOFF below).  The "retries" value should be at
  69. ### least 1, because the first attempt counts, too (the name "retries"
  70. ### is confusing, sorry for that).
  71. ###
  72. my $default_retries = 5;
  73. ### Default backoff factor for SNMP_Session objects.  This factor is
  74. ### used to increase the TIMEOUT every time an SNMP request is
  75. ### retried.
  76. ###
  77. my $default_backoff = 1.0;
  78. ### Default value for maxRepetitions.  This specifies how many table
  79. ### rows are requested in getBulk requests.  Used when walking tables
  80. ### using getBulk (only available in SNMPv2(c) and later).  If this is
  81. ### too small, then a table walk will need unnecessarily many
  82. ### request/response exchanges.  If it is too big, the agent may
  83. ### compute many variables after the end of the table.  It is
  84. ### recommended to set this explicitly for each table walk by using
  85. ### map_table_4().
  86. ###
  87. my $default_max_repetitions = 12;
  88. ### Default value for "avoid_negative_request_ids".
  89. ###
  90. ### Set this to non-zero if you have agents that have trouble with
  91. ### negative request IDs, and don't forget to complain to your agent
  92. ### vendor.  According to the spec (RFC 1905), the request-id is an
  93. ### Integer32, i.e. its range is from -(2^31) to (2^31)-1.  However,
  94. ### some agents erroneously encode the response ID as an unsigned,
  95. ### which prevents this code from matching such responses to requests.
  96. ###
  97. $SNMP_Session::default_avoid_negative_request_ids = 0;
  98. ### Default value for "use_16bit_request_ids".
  99. ###
  100. ### Set this to non-zero if you have agents that use 16bit request IDs,
  101. ### and don't forget to complain to your agent vendor.
  102. ###
  103. $SNMP_Session::default_use_16bit_request_ids = 0;
  104. ### Whether all SNMP_Session objects should share a single UDP socket.
  105. ###
  106. $SNMP_Session::recycle_socket = 0;
  107. ### IPv6 initialization code: check that IPv6 libraries are available,
  108. ### and if so load them.
  109. ### We store the length of an IPv6 socket address structure in the class
  110. ### so we can determine if a socket address is IPv4 or IPv6 just by checking
  111. ### its length. The proper way to do this would be to use sockaddr_family(),
  112. ### but this function is only available in recent versions of Socket.pm.
  113. my $ipv6_addr_len;
  114. BEGIN {
  115.     $ipv6_addr_len = undef;
  116.     $SNMP_Session::ipv6available = 0;
  117.     if (eval {require Socket6;} &&
  118. eval {require IO::Socket::INET6; IO::Socket::INET6->VERSION("1.26");}) {
  119. import Socket6;
  120. $ipv6_addr_len = length(pack_sockaddr_in6(161, inet_pton(AF_INET6(), "::1")));
  121. $SNMP_Session::ipv6available = 1;
  122.     }
  123. }
  124. my $the_socket;
  125. $SNMP_Session::errmsg = '';
  126. $SNMP_Session::suppress_warnings = 0;
  127. sub get_request      { 0 | context_flag };
  128. sub getnext_request  { 1 | context_flag };
  129. sub get_response     { 2 | context_flag };
  130. sub set_request      { 3 | context_flag };
  131. sub trap_request     { 4 | context_flag };
  132. sub getbulk_request  { 5 | context_flag };
  133. sub inform_request   { 6 | context_flag };
  134. sub trap2_request    { 7 | context_flag };
  135. sub standard_udp_port { 161 };
  136. sub open
  137. {
  138.     return SNMPv1_Session::open (@_);
  139. }
  140. sub timeout { $_[0]->{timeout} }
  141. sub retries { $_[0]->{retries} }
  142. sub backoff { $_[0]->{backoff} }
  143. sub set_timeout {
  144.     my ($session, $timeout) = @_;
  145.     croak ("timeout ($timeout) must be a positive number") unless $timeout > 0.0;
  146.     $session->{'timeout'} = $timeout;
  147. }
  148. sub set_retries {
  149.     my ($session, $retries) = @_;
  150.     croak ("retries ($retries) must be a non-negative integer")
  151. unless $retries == int ($retries) && $retries >= 0;
  152.     $session->{'retries'} = $retries; 
  153. }
  154. sub set_backoff {
  155.     my ($session, $backoff) = @_;
  156.     croak ("backoff ($backoff) must be a number >= 1.0")
  157. unless $backoff == int ($backoff) && $backoff >= 1.0;
  158.     $session->{'backoff'} = $backoff; 
  159. }
  160. sub encode_request_3 ($$$@) {
  161.     my($this, $reqtype, $encoded_oids_or_pairs, $i1, $i2) = @_;
  162.     my($request);
  163.     local($_);
  164.     $this->{request_id} = ($this->{request_id} == 0x7fffffff)
  165. ? -0x80000000 : $this->{request_id}+1;
  166.     $this->{request_id} += 0x80000000
  167. if ($this->{avoid_negative_request_ids} && $this->{request_id} < 0);
  168.     $this->{request_id} &= 0x0000ffff
  169. if ($this->{use_16bit_request_ids});
  170.     foreach $_ (@{$encoded_oids_or_pairs}) {
  171.       if (ref ($_) eq 'ARRAY') {
  172. $_ = &encode_sequence ($_->[0], $_->[1])
  173.   || return $this->ber_error ("encoding pair");
  174.       } else {
  175. $_ = &encode_sequence ($_, encode_null())
  176.   || return $this->ber_error ("encoding value/null pair");
  177.       }
  178.     }
  179.     $request = encode_tagged_sequence
  180. ($reqtype,
  181.  encode_int ($this->{request_id}),
  182.  defined $i1 ? encode_int ($i1) : encode_int_0 (),
  183.  defined $i2 ? encode_int ($i2) : encode_int_0 (),
  184.  encode_sequence (@{$encoded_oids_or_pairs}))
  185.   || return $this->ber_error ("encoding request PDU");
  186.     return $this->wrap_request ($request);
  187. }
  188. sub encode_get_request {
  189.     my($this, @oids) = @_;
  190.     return encode_request_3 ($this, get_request, @oids);
  191. }
  192. sub encode_getnext_request {
  193.     my($this, @oids) = @_;
  194.     return encode_request_3 ($this, getnext_request, @oids);
  195. }
  196. sub encode_getbulk_request {
  197.     my($this, $non_repeaters, $max_repetitions, @oids) = @_;
  198.     return encode_request_3 ($this, getbulk_request, @oids,
  199.      $non_repeaters, $max_repetitions);
  200. }
  201. sub encode_set_request {
  202.     my($this, @encoded_pairs) = @_;
  203.     return encode_request_3 ($this, set_request, @encoded_pairs);
  204. }
  205. sub encode_trap_request ($$$$$$@) {
  206.     my($this, $ent, $agent, $gen, $spec, $dt, @pairs) = @_;
  207.     my($request);
  208.     local($_);
  209.     foreach $_ (@pairs) {
  210.       if (ref ($_) eq 'ARRAY') {
  211. $_ = &encode_sequence ($_->[0], $_->[1])
  212.   || return $this->ber_error ("encoding pair");
  213.       } else {
  214. $_ = &encode_sequence ($_, encode_null())
  215.   || return $this->ber_error ("encoding value/null pair");
  216.       }
  217.     }
  218.     $request = encode_tagged_sequence
  219. (trap_request, $ent, $agent, $gen, $spec, $dt, encode_sequence (@pairs))
  220.   || return $this->ber_error ("encoding trap PDU");
  221.     return $this->wrap_request ($request);
  222. }
  223. sub encode_v2_trap_request ($@) {
  224.     my($this, @pairs) = @_;
  225.     return encode_request_3($this, trap2_request, @pairs);
  226. }
  227. sub decode_get_response {
  228.     my($this, $response) = @_;
  229.     my @rest;
  230.     @{$this->{'unwrapped'}};
  231. }
  232. sub decode_trap_request ($$) {
  233.     my ($this, $trap) = @_;
  234.     my ($snmp_version, $community, $ent, $agent, $gen, $spec, $dt,
  235. $request_id, $error_status, $error_index,
  236. $bindings);
  237.     ($snmp_version, $community,
  238.      $ent, $agent,
  239.      $gen, $spec, $dt,
  240.      $bindings)
  241. = decode_by_template ($trap, "%{%i%s%*{%O%A%i%i%u%{%@",
  242.     trap_request);
  243.     if (! defined ($snmp_version)) {
  244. ($snmp_version, $community,
  245.  $request_id, $error_status, $error_index,
  246.  $bindings)
  247.     = decode_by_template ($trap, "%{%i%s%*{%i%i%i%{%@",
  248.   trap2_request);
  249. return $this->error_return ("v2 trap request contained errorStatus/errorIndex "
  250.       .$error_status."/".$error_index)
  251.     if defined $error_status && defined $error_index
  252.     && ($error_status != 0 || $error_index != 0);
  253.     }
  254.     if (!defined $snmp_version) {
  255. return $this->error_return ("BER error decoding trap:n  ".$BER::errmsg);
  256.     }
  257.     return ($community, $ent, $agent, $gen, $spec, $dt, $bindings);
  258. }
  259. sub wait_for_response {
  260.     my($this) = shift;
  261.     my($timeout) = shift || 10.0;
  262.     my($rin,$win,$ein) = ('','','');
  263.     my($rout,$wout,$eout);
  264.     vec($rin,$this->sockfileno,1) = 1;
  265.     select($rout=$rin,$wout=$win,$eout=$ein,$timeout);
  266. }
  267. sub get_request_response ($@) {
  268.     my($this, @oids) = @_;
  269.     return $this->request_response_5 ($this->encode_get_request (@oids),
  270.       get_response, @oids, 1);
  271. }
  272. sub set_request_response ($@) {
  273.     my($this, @pairs) = @_;
  274.     return $this->request_response_5 ($this->encode_set_request (@pairs),
  275.       get_response, @pairs, 1);
  276. }
  277. sub getnext_request_response ($@) {
  278.     my($this,@oids) = @_;
  279.     return $this->request_response_5 ($this->encode_getnext_request (@oids),
  280.       get_response, @oids, 1);
  281. }
  282. sub getbulk_request_response ($$$@) {
  283.     my($this,$non_repeaters,$max_repetitions,@oids) = @_;
  284.     return $this->request_response_5
  285. ($this->encode_getbulk_request ($non_repeaters,$max_repetitions,@oids),
  286.  get_response, @oids, 1);
  287. }
  288. sub trap_request_send ($$$$$$@) {
  289.     my($this, $ent, $agent, $gen, $spec, $dt, @pairs) = @_;
  290.     my($req);
  291.     $req = $this->encode_trap_request ($ent, $agent, $gen, $spec, $dt, @pairs);
  292.     ## Encoding may have returned an error.
  293.     return undef unless defined $req;
  294.     $this->send_query($req)
  295. || return $this->error ("send_trap: $!");
  296.     return 1;
  297. }
  298. sub v2_trap_request_send ($$$@) {
  299.     my($this, $trap_oid, $dt, @pairs) = @_;
  300.     my @sysUptime_OID = ( 1,3,6,1,2,1,1,3 );
  301.     my @snmpTrapOID_OID = ( 1,3,6,1,6,3,1,1,4,1 );
  302.     my($req);
  303.     unshift @pairs, [encode_oid (@snmpTrapOID_OID,0),
  304.      encode_oid (@{$trap_oid})];
  305.     unshift @pairs, [encode_oid (@sysUptime_OID,0),
  306.      encode_timeticks ($dt)];
  307.     $req = $this->encode_v2_trap_request (@pairs);
  308.     ## Encoding may have returned an error.
  309.     return undef unless defined $req;
  310.     $this->send_query($req)
  311. || return $this->error ("send_trap: $!");
  312.     return 1;
  313. }
  314. sub request_response_5 ($$$$$) {
  315.     my ($this, $req, $response_tag, $oids, $errorp) = @_;
  316.     my $retries = $this->retries;
  317.     my $timeout = $this->timeout;
  318.     my ($nfound, $timeleft);
  319.     ## Encoding may have returned an error.
  320.     return undef unless defined $req;
  321.     $timeleft = $timeout;
  322.     while ($retries > 0) {
  323. $this->send_query ($req)
  324.     || return $this->error ("send_query: $!");
  325. # IlvJa
  326. # Add request pdu to capture_buffer
  327. push @{$this->{'capture_buffer'}}, $req
  328.     if (defined $this->{'capture_buffer'}
  329. and ref $this->{'capture_buffer'} eq 'ARRAY');
  330. #
  331.       wait_for_response:
  332. ($nfound, $timeleft) = $this->wait_for_response($timeleft);
  333. if ($nfound > 0) {
  334.     my($response_length);
  335.     $response_length
  336. = $this->receive_response_3 ($response_tag, $oids, $errorp);
  337.     if ($response_length) {
  338. # IlvJa
  339. # Add response pdu to capture_buffer
  340. push (@{$this->{'capture_buffer'}},
  341.       substr($this->{'pdu_buffer'}, 0, $response_length)
  342.       )
  343.       if (defined $this->{'capture_buffer'}
  344.   and ref $this->{'capture_buffer'} eq 'ARRAY');
  345. #
  346. return $response_length;
  347.     } elsif (defined ($response_length)) {
  348. goto wait_for_response;
  349. # A response has been received, but for a different
  350. # request ID or from a different IP address.
  351.     } else {
  352. return undef;
  353.     }
  354. } else {
  355.     ## No response received - retry
  356.     --$retries;
  357.     $timeout *= $this->backoff;
  358.     $timeleft = $timeout;
  359. }
  360.     }
  361.     # IlvJa
  362.     # Add empty packet to capture_buffer
  363.     push @{$this->{'capture_buffer'}}, "" 
  364. if (defined $this->{'capture_buffer'}
  365.     and ref $this->{'capture_buffer'} eq 'ARRAY');
  366.     #
  367.     $this->error ("no response received");
  368. }
  369. sub map_table ($$$) {
  370.     my ($session, $columns, $mapfn) = @_;
  371.     return $session->map_table_4 ($columns, $mapfn,
  372.   $session->default_max_repetitions ());
  373. }
  374. sub map_table_4 ($$$$) {
  375.     my ($session, $columns, $mapfn, $max_repetitions) = @_;
  376.     return $session->map_table_start_end ($columns, $mapfn,
  377.   "", undef,
  378.   $max_repetitions);
  379. }
  380. sub map_table_start_end ($$$$$$) {
  381.     my ($session, $columns, $mapfn, $start, $end, $max_repetitions) = @_;
  382.     my @encoded_oids;
  383.     my $call_counter = 0;
  384.     my $base_index = $start;
  385.     do {
  386. foreach (@encoded_oids = @{$columns}) {
  387.     $_=encode_oid (@{$_},split '.',$base_index)
  388. || return $session->ber_error ("encoding OID $base_index");
  389. }
  390. if ($session->getnext_request_response (@encoded_oids)) {
  391.     my $response = $session->pdu_buffer;
  392.     my ($bindings) = $session->decode_get_response ($response);
  393.     my $smallest_index = undef;
  394.     my @collected_values = ();
  395.     my @bases = @{$columns};
  396.     while ($bindings ne '') {
  397. my ($binding, $oid, $value);
  398. my $base = shift @bases;
  399. ($binding, $bindings) = decode_sequence ($bindings);
  400. ($oid, $value) = decode_by_template ($binding, "%O%@");
  401. my $out_index;
  402. $out_index = &oid_diff ($base, $oid);
  403. my $cmp;
  404. if (!defined $smallest_index
  405.     || ($cmp = index_compare ($out_index,$smallest_index)) == -1) {
  406.     $smallest_index = $out_index;
  407.     grep ($_=undef, @collected_values);
  408.     push @collected_values, $value;
  409. } elsif ($cmp == 1) {
  410.     push @collected_values, undef;
  411. } else {
  412.     push @collected_values, $value;
  413. }
  414.     }
  415.     (++$call_counter,
  416.      &$mapfn ($smallest_index, @collected_values))
  417. if defined $smallest_index;
  418.     $base_index = $smallest_index;
  419. } else {
  420.     return undef;
  421. }
  422.     }
  423.     while (defined $base_index
  424.    && (!defined $end || index_compare ($base_index, $end) < 0));
  425.     $call_counter;
  426. }
  427. sub index_compare ($$) {
  428.   my ($i1, $i2) = @_;
  429.   $i1 = '' unless defined $i1;
  430.   $i2 = '' unless defined $i2;
  431.   if ($i1 eq '') {
  432.       return $i2 eq '' ? 0 : 1;
  433.   } elsif ($i2 eq '') {
  434.       return 1;
  435.   } elsif (!$i1) {
  436.       return $i2 eq '' ? 1 : !$i2 ? 0 : 1;
  437.   } elsif (!$i2) {
  438.       return -1;
  439.   } else {
  440.     my ($f1,$r1) = split('.',$i1,2);
  441.     my ($f2,$r2) = split('.',$i2,2);
  442.     if ($f1 < $f2) {
  443.       return -1;
  444.     } elsif ($f1 > $f2) {
  445.       return 1;
  446.     } else {
  447.       return index_compare ($r1,$r2);
  448.     }
  449.   }
  450. }
  451. sub oid_diff ($$) {
  452.   my($base, $full) = @_;
  453.   my $base_dotnot = join ('.',@{$base});
  454.   my $full_dotnot = BER::pretty_oid ($full);
  455.   return undef unless substr ($full_dotnot, 0, length $base_dotnot)
  456.     eq $base_dotnot
  457.       && substr ($full_dotnot, length $base_dotnot, 1) eq '.';
  458.   substr ($full_dotnot, length ($base_dotnot)+1);
  459. }
  460. # Pretty_address returns a human-readable representation of an IPv4 or IPv6 address.
  461. sub pretty_address {
  462.     my($addr) = shift;
  463.     my($port, $addrunpack, $addrstr);
  464.     # Disable strict subs to stop old versions of perl from
  465.     # complaining about AF_INET6 when Socket6 is not available
  466.     if( (defined $ipv6_addr_len) && (length $addr == $ipv6_addr_len)) {
  467. ($port,$addrunpack) = unpack_sockaddr_in6 ($addr);
  468. $addrstr = inet_ntop (AF_INET6(), $addrunpack);
  469.     } else {
  470. ($port,$addrunpack) = unpack_sockaddr_in ($addr);
  471. $addrstr = inet_ntoa ($addrunpack);
  472.     }
  473.     return sprintf ("[%s].%d", $addrstr, $port);
  474. }
  475. sub version { $VERSION; }
  476. sub error_return ($$) {
  477.     my ($this,$message) = @_;
  478.     $SNMP_Session::errmsg = $message;
  479.     unless ($SNMP_Session::suppress_warnings) {
  480. $message =~ s/^/  /mg;
  481. carp ("Error:n".$message."n");
  482.     }
  483.     return undef;
  484. }
  485. sub error ($$) {
  486.     my ($this,$message) = @_;
  487.     my $session = $this->to_string;
  488.     $SNMP_Session::errmsg = $message."n".$session;
  489.     unless ($SNMP_Session::suppress_warnings) {
  490. $session =~ s/^/  /mg;
  491. $message =~ s/^/  /mg;
  492. carp ("SNMP Error:n".$SNMP_Session::errmsg."n");
  493.     }
  494.     return undef;
  495. }
  496. sub ber_error ($$) {
  497.   my ($this,$type) = @_;
  498.   my ($errmsg) = $BER::errmsg;
  499.   $errmsg =~ s/^/  /mg;
  500.   return $this->error ("$type:n$errmsg");
  501. }
  502. package SNMPv1_Session;
  503. use strict qw(vars subs); # see above
  504. use vars qw(@ISA);
  505. use SNMP_Session;
  506. use Socket;
  507. use BER;
  508. use IO::Socket;
  509. use Carp;
  510. BEGIN {
  511.     if($SNMP_Session::ipv6available) {
  512. import IO::Socket::INET6;
  513. import Socket6;
  514.     }
  515. }
  516. @ISA = qw(SNMP_Session);
  517. sub snmp_version { 0 }
  518. # Supports both IPv4 and IPv6.
  519. # Numeric IPv6 addresses must be passed between square brackets []
  520. sub open {
  521.     my($this,
  522.        $remote_hostname,$community,$port,
  523.        $max_pdu_len,$local_port,$max_repetitions,
  524.        $local_hostname,$ipv4only) = @_;
  525.     my($remote_addr,$socket,$sockfamily);
  526.     $ipv4only = 1 unless defined $ipv4only;
  527.     $sockfamily = AF_INET;
  528.     $community = 'public' unless defined $community;
  529.     $port = SNMP_Session::standard_udp_port unless defined $port;
  530.     $max_pdu_len = 8000 unless defined $max_pdu_len;
  531.     $max_repetitions = $default_max_repetitions
  532. unless defined $max_repetitions;
  533.     if ($ipv4only || ! $SNMP_Session::ipv6available) {
  534. # IPv4-only code, uses only Socket and INET calls
  535.     if (defined $remote_hostname) {
  536. $remote_addr = inet_aton ($remote_hostname)
  537.     or return $this->error_return ("can't resolve "$remote_hostname" to IP address");
  538.     }
  539.     if ($SNMP_Session::recycle_socket && defined $the_socket) {
  540. $socket = $the_socket;
  541.     } else {
  542. $socket = IO::Socket::INET->new(Proto => 17,
  543. Type => SOCK_DGRAM,
  544. LocalAddr => $local_hostname,
  545. LocalPort => $local_port)
  546.     || return $this->error_return ("creating socket: $!");
  547. $the_socket = $socket
  548.     if $SNMP_Session::recycle_socket;
  549.     }
  550.     $remote_addr = pack_sockaddr_in ($port, $remote_addr)
  551. if defined $remote_addr;
  552.     } else {
  553. # IPv6-capable code. Will use IPv6 or IPv4 depending on the address.
  554. # Uses Socket6 and INET6 calls.
  555. # If it's a numeric IPv6 addresses, remove square brackets
  556. if ($remote_hostname =~ /^[(.*)]$/) {
  557.     $remote_hostname = $1;
  558. }
  559. my (@res, $socktype_tmp, $proto_tmp, $canonname_tmp);
  560. @res = getaddrinfo($remote_hostname, $port, AF_UNSPEC, SOCK_DGRAM);
  561. ($sockfamily, $socktype_tmp, $proto_tmp, $remote_addr, $canonname_tmp) = @res;
  562. if (scalar(@res) < 5) {
  563.     return $this->error_return ("can't resolve "$remote_hostname" to IPv6 address");
  564. }
  565. if ($SNMP_Session::recycle_socket && defined $the_socket) {
  566.     $socket = $the_socket;
  567. } elsif ($sockfamily == AF_INET) {
  568.     $socket = IO::Socket::INET->new(Proto => 17,
  569.     Type => SOCK_DGRAM,
  570.     LocalAddr => $local_hostname,
  571.     LocalPort => $local_port)
  572.          || return $this->error_return ("creating socket: $!");
  573. } else {
  574.     $socket = IO::Socket::INET6->new(Proto => 17,
  575.      Type => SOCK_DGRAM,
  576.      LocalAddr => $local_hostname,
  577.      LocalPort => $local_port)
  578.          || return $this->error_return ("creating socket: $!");
  579.     $the_socket = $socket
  580.         if $SNMP_Session::recycle_socket;
  581. }
  582.     }
  583.     bless {
  584.    'sock' => $socket,
  585.    'sockfileno' => fileno ($socket),
  586.    'community' => $community,
  587.    'remote_hostname' => $remote_hostname,
  588.    'remote_addr' => $remote_addr,
  589.    'sockfamily' => $sockfamily,
  590.    'max_pdu_len' => $max_pdu_len,
  591.    'pdu_buffer' => '' x $max_pdu_len,
  592.    'request_id' => (int (rand 0x10000) << 16)
  593.        + int (rand 0x10000) - 0x80000000,
  594.    'timeout' => $default_timeout,
  595.    'retries' => $default_retries,
  596.    'backoff' => $default_backoff,
  597.    'debug' => $default_debug,
  598.    'error_status' => 0,
  599.    'error_index' => 0,
  600.    'default_max_repetitions' => $max_repetitions,
  601.    'use_getbulk' => 1,
  602.    'lenient_source_address_matching' => 1,
  603.    'lenient_source_port_matching' => 1,
  604.    'avoid_negative_request_ids' => $SNMP_Session::default_avoid_negative_request_ids,
  605.    'use_16bit_request_ids' => $SNMP_Session::default_use_16bit_request_ids,
  606.    'capture_buffer' => undef,
  607.   };
  608. }
  609. sub open_trap_session (@) {
  610.     my ($this, $port) = @_;
  611.     $port = 162 unless defined $port;
  612.     return $this->open (undef, "", 161, undef, $port);
  613. }
  614. sub sock { $_[0]->{sock} }
  615. sub sockfileno { $_[0]->{sockfileno} }
  616. sub remote_addr { $_[0]->{remote_addr} }
  617. sub pdu_buffer { $_[0]->{pdu_buffer} }
  618. sub max_pdu_len { $_[0]->{max_pdu_len} }
  619. sub default_max_repetitions {
  620.     defined $_[1]
  621. ? $_[0]->{default_max_repetitions} = $_[1]
  622.     : $_[0]->{default_max_repetitions} }
  623. sub debug { defined $_[1] ? $_[0]->{debug} = $_[1] : $_[0]->{debug} }
  624. sub close {
  625.     my($this) = shift;
  626.     ## Avoid closing the socket if it may be shared with other session
  627.     ## objects.
  628.     if (! defined $the_socket || $this->sock ne $the_socket) {
  629. close ($this->sock) || $this->error ("close: $!");
  630.     }
  631. }
  632. sub wrap_request {
  633.     my($this) = shift;
  634.     my($request) = shift;
  635.     encode_sequence (encode_int ($this->snmp_version),
  636.      encode_string ($this->{community}),
  637.      $request)
  638.       || return $this->ber_error ("wrapping up request PDU");
  639. }
  640. my @error_status_code = qw(noError tooBig noSuchName badValue readOnly
  641.    genErr noAccess wrongType wrongLength
  642.    wrongEncoding wrongValue noCreation
  643.    inconsistentValue resourceUnavailable
  644.    commitFailed undoFailed authorizationError
  645.    notWritable inconsistentName);
  646. sub unwrap_response_5b {
  647.     my ($this,$response,$tag,$oids,$errorp) = @_;
  648.     my ($community,$request_id,@rest,$snmpver);
  649.     ($snmpver,$community,$request_id,
  650.      $this->{error_status},
  651.      $this->{error_index},
  652.      @rest)
  653. = decode_by_template ($response, "%{%i%s%*{%i%i%i%{%@",
  654.       $tag);
  655.     return $this->ber_error ("Error decoding response PDU")
  656.       unless defined $snmpver;
  657.     return $this->error ("Received SNMP response with unknown snmp-version field $snmpver")
  658. unless $snmpver == $this->snmp_version;
  659.     if ($this->{error_status} != 0) {
  660.       if ($errorp) {
  661. my ($oid, $errmsg);
  662. $errmsg = $error_status_code[$this->{error_status}] || $this->{error_status};
  663. $oid = $oids->[$this->{error_index}-1]
  664.   if $this->{error_index} > 0 && $this->{error_index}-1 <= $#{$oids};
  665. $oid = $oid->[0]
  666.   if ref($oid) eq 'ARRAY';
  667. return ($community, $request_id,
  668. $this->error ("Received SNMP response with error coden"
  669.       ."  error status: $errmsgn"
  670.       ."  index ".$this->{error_index}
  671.       .(defined $oid
  672. ? " (OID: ".&BER::pretty_oid($oid).")"
  673. : "")));
  674.       } else {
  675. if ($this->{error_index} == 1) {
  676.   @rest[$this->{error_index}-1..$this->{error_index}] = ();
  677. }
  678.       }
  679.     }
  680.     ($community, $request_id, @rest);
  681. }
  682. sub send_query ($$) {
  683.     my ($this,$query) = @_;
  684.     send ($this->sock,$query,0,$this->remote_addr);
  685. }
  686. ## Compare two sockaddr_in structures for equality.  This is used when
  687. ## matching incoming responses with outstanding requests.  Previous
  688. ## versions of the code simply did a bytewise comparison ("eq") of the
  689. ## two sockaddr_in structures, but this didn't work on some systems
  690. ## where sockaddr_in contains other elements than just the IP address
  691. ## and port number, notably FreeBSD.
  692. ##
  693. ## We allow for varying degrees of leniency when checking the source
  694. ## address.  By default we now ignore it altogether, because there are
  695. ## agents that don't respond from UDP port 161, and there are agents
  696. ## that don't respond from the IP address the query had been sent to.
  697. ##
  698. ## The address family is stored in the session object. We could use
  699. ## sockaddr_family() to determine it from the sockaddr, but this function
  700. ## is only available in recent versions of Socket.pm.
  701. sub sa_equal_p ($$$) {
  702.     my ($this, $sa1, $sa2) = @_;
  703.     my ($p1,$a1,$p2,$a2);
  704.     # Disable strict subs to stop old versions of perl from
  705.     # complaining about AF_INET6 when Socket6 is not available
  706.     if($this->{'sockfamily'} == AF_INET) {
  707. # IPv4 addresses
  708. ($p1,$a1) = unpack_sockaddr_in ($sa1);
  709. ($p2,$a2) = unpack_sockaddr_in ($sa2);
  710.     } elsif($this->{'sockfamily'} == AF_INET6()) {
  711. # IPv6 addresses
  712. ($p1,$a1) = unpack_sockaddr_in6 ($sa1);
  713. ($p2,$a2) = unpack_sockaddr_in6 ($sa2);
  714.     } else {
  715. return 0;
  716.     }
  717.     use strict "subs";
  718.     if (! $this->{'lenient_source_address_matching'}) {
  719. return 0 if $a1 ne $a2;
  720.     }
  721.     if (! $this->{'lenient_source_port_matching'}) {
  722. return 0 if $p1 != $p2;
  723.     }
  724.     return 1;
  725. }
  726. sub receive_response_3 {
  727.     my ($this, $response_tag, $oids, $errorp) = @_;
  728.     my ($remote_addr);
  729.     $remote_addr = recv ($this->sock,$this->{'pdu_buffer'},$this->max_pdu_len,0);
  730.     return $this->error ("receiving response PDU: $!")
  731. unless defined $remote_addr;
  732.     return $this->error ("short (".length $this->{'pdu_buffer'}
  733.  ." bytes) response PDU")
  734. unless length $this->{'pdu_buffer'} > 2;
  735.     my $response = $this->{'pdu_buffer'};
  736.     ##
  737.     ## Check whether the response came from the address we've sent the
  738.     ## request to.  If this is not the case, we should probably ignore
  739.     ## it, as it may relate to another request.
  740.     ##
  741.     if (defined $this->{'remote_addr'}) {
  742. if (! $this->sa_equal_p ($remote_addr, $this->{'remote_addr'})) {
  743.     if ($this->{'debug'} && !$SNMP_Session::recycle_socket) {
  744. carp ("Response came from ".&SNMP_Session::pretty_address($remote_addr)
  745.       .", not ".&SNMP_Session::pretty_address($this->{'remote_addr'}))
  746. unless $SNMP_Session::suppress_warnings;
  747.     }
  748.     return 0;
  749. }
  750.     }
  751.     $this->{'last_sender_addr'} = $remote_addr;
  752.     my ($response_community, $response_id, @unwrapped)
  753. = $this->unwrap_response_5b ($response, $response_tag,
  754.      $oids, $errorp);
  755.     if ($response_community ne $this->{community}
  756.         || $response_id ne $this->{request_id}) {
  757. if ($this->{'debug'}) {
  758.     carp ("$response_community != $this->{community}")
  759. unless $SNMP_Session::suppress_warnings
  760.     || $response_community eq $this->{community};
  761.     carp ("$response_id != $this->{request_id}")
  762. unless $SNMP_Session::suppress_warnings
  763.     || $response_id == $this->{request_id};
  764. }
  765. return 0;
  766.     }
  767.     if (!defined $unwrapped[0]) {
  768. $this->{'unwrapped'} = undef;
  769. return undef;
  770.     }
  771.     $this->{'unwrapped'} = @unwrapped;
  772.     return length $this->pdu_buffer;
  773. }
  774. sub receive_trap {
  775.     my ($this) = @_;
  776.     my ($remote_addr, $iaddr, $port, $trap);
  777.     $remote_addr = recv ($this->sock,$this->{'pdu_buffer'},$this->max_pdu_len,0);
  778.     return undef unless $remote_addr;
  779.     if( (defined $ipv6_addr_len) && (length $remote_addr == $ipv6_addr_len)) {
  780. ($port,$iaddr) = unpack_sockaddr_in6($remote_addr);
  781.     } else {
  782. ($port,$iaddr) = unpack_sockaddr_in($remote_addr);
  783.     }
  784.     $trap = $this->{'pdu_buffer'};
  785.     return ($trap, $iaddr, $port);
  786. }
  787. sub describe {
  788.     my($this) = shift;
  789.     print $this->to_string (),"n";
  790. }
  791. sub to_string {
  792.     my($this) = shift;
  793.     my ($class,$prefix);
  794.     $class = ref($this);
  795.     $prefix = ' ' x (length ($class) + 2);
  796.     ($class
  797.      .(defined $this->{remote_hostname}
  798.        ? " (remote host: "".$this->{remote_hostname}."""
  799.        ." ".&SNMP_Session::pretty_address ($this->remote_addr).")"
  800.        : " (no remote host specified)")
  801.      ."n"
  802.      .$prefix."  community: "".$this->{'community'}.""n"
  803.      .$prefix." request ID: ".$this->{'request_id'}."n"
  804.      .$prefix."PDU bufsize: ".$this->{'max_pdu_len'}." bytesn"
  805.      .$prefix."    timeout: ".$this->{timeout}."sn"
  806.      .$prefix."    retries: ".$this->{retries}."n"
  807.      .$prefix."    backoff: ".$this->{backoff}.")");
  808. ##    sprintf ("SNMP_Session: %s (size %d timeout %g)",
  809. ##    &SNMP_Session::pretty_address ($this->remote_addr),$this->max_pdu_len,
  810. ##        $this->timeout);
  811. }
  812. ### SNMP Agent support
  813. ### contributed by Mike McCauley <mikem@open.com.au>
  814. ###
  815. sub receive_request {
  816.     my ($this) = @_;
  817.     my ($remote_addr, $iaddr, $port, $request);
  818.     $remote_addr = recv($this->sock, $this->{'pdu_buffer'}, 
  819. $this->{'max_pdu_len'}, 0);
  820.     return undef unless $remote_addr;
  821.     if( (defined $ipv6_addr_len) && (length $remote_addr == $ipv6_addr_len)) {
  822. ($port,$iaddr) = unpack_sockaddr_in6($remote_addr);
  823.     } else {
  824. ($port,$iaddr) = unpack_sockaddr_in($remote_addr);
  825.     }
  826.     $request = $this->{'pdu_buffer'};
  827.     return ($request, $iaddr, $port);
  828. }
  829. sub decode_request {
  830.     my ($this, $request) = @_;
  831.     my ($snmp_version, $community, $requestid, $errorstatus, $errorindex, $bindings);
  832.     ($snmp_version, $community, $requestid, $errorstatus, $errorindex, $bindings)
  833. = decode_by_template ($request, "%{%i%s%*{%i%i%i%@", SNMP_Session::get_request);
  834.     if (defined $snmp_version)
  835.     {
  836. # Its a valid get_request
  837. return(SNMP_Session::get_request, $requestid, $bindings, $community);
  838.     }
  839.     ($snmp_version, $community, $requestid, $errorstatus, $errorindex, $bindings)
  840. = decode_by_template ($request, "%{%i%s%*{%i%i%i%@", SNMP_Session::getnext_request);
  841.     if (defined $snmp_version)
  842.     {
  843. # Its a valid getnext_request
  844. return(SNMP_Session::getnext_request, $requestid, $bindings, $community);
  845.     }
  846.     ($snmp_version, $community, $requestid, $errorstatus, $errorindex, $bindings)
  847. = decode_by_template ($request, "%{%i%s%*{%i%i%i%@", SNMP_Session::set_request);
  848.     if (defined $snmp_version)
  849.     {
  850. # Its a valid set_request
  851. return(SNMP_Session::set_request, $requestid, $bindings, $community);
  852.     }
  853.     # Something wrong with this packet
  854.     # Decode failed
  855.     return undef;
  856. }
  857. package SNMPv2c_Session;
  858. use strict qw(vars subs); # see above
  859. use vars qw(@ISA);
  860. use SNMP_Session;
  861. use BER;
  862. use Carp;
  863. @ISA = qw(SNMPv1_Session);
  864. sub snmp_version { 1 }
  865. sub open {
  866.     my $session = SNMPv1_Session::open (@_);
  867.     return undef unless defined $session;
  868.     return bless $session;
  869. }
  870. ## map_table_start_end using get-bulk
  871. ##
  872. sub map_table_start_end ($$$$$$) {
  873.     my ($session, $columns, $mapfn, $start, $end, $max_repetitions) = @_;
  874.     my @encoded_oids;
  875.     my $call_counter = 0;
  876.     my $base_index = $start;
  877.     my $ncols = @{$columns};
  878.     my @collected_values = ();
  879.     if (! $session->{'use_getbulk'}) {
  880. return SNMP_Session::map_table_start_end
  881.     ($session, $columns, $mapfn, $start, $end, $max_repetitions);
  882.     }
  883.     $max_repetitions = $session->default_max_repetitions
  884. unless defined $max_repetitions;
  885.     for (;;) {
  886. foreach (@encoded_oids = @{$columns}) {
  887.     $_=encode_oid (@{$_},split '.',$base_index)
  888. || return $session->ber_error ("encoding OID $base_index");
  889. }
  890. if ($session->getbulk_request_response (0, $max_repetitions,
  891. @encoded_oids)) {
  892.     my $response = $session->pdu_buffer;
  893.     my ($bindings) = $session->decode_get_response ($response);
  894.     my @colstack = ();
  895.     my $k = 0;
  896.     my $j;
  897.     my $min_index = undef;
  898.     my @bases = @{$columns};
  899.     my $n_bindings = 0;
  900.     my $binding;
  901.     ## Copy all bindings into the colstack.
  902.     ## The colstack is a vector of vectors.
  903.     ## It contains one vector for each "repeater" variable.
  904.     ##
  905.     while ($bindings ne '') {
  906. ($binding, $bindings) = decode_sequence ($bindings);
  907. my ($oid, $value) = decode_by_template ($binding, "%O%@");
  908. push @{$colstack[$k]}, [$oid, $value];
  909. ++$k; $k = 0 if $k >= $ncols;
  910.     }
  911.     ## Now collect rows from the column stack:
  912.     ##
  913.     ## Iterate through the column stacks to find the smallest
  914.     ## index, collecting the values for that index in
  915.     ## @collected_values.
  916.     ##
  917.     ## As long as a row can be assembled, the map function is
  918.     ## called on it and the iteration proceeds.
  919.     ##
  920.     $base_index = undef;
  921.   walk_rows_from_pdu:
  922.     for (;;) {
  923. my $min_index = undef;
  924. for ($k = 0; $k < $ncols; ++$k) {
  925.     $collected_values[$k] = undef;
  926.     my $pair = $colstack[$k]->[0];
  927.     unless (defined $pair) {
  928. $min_index = undef;
  929. last walk_rows_from_pdu;
  930.     }
  931.     my $this_index
  932. = SNMP_Session::oid_diff ($columns->[$k], $pair->[0]);
  933.     if (defined $this_index) {
  934. my $cmp
  935.     = !defined $min_index
  936. ? -1
  937.     : SNMP_Session::index_compare
  938. ($this_index, $min_index);
  939. if ($cmp == -1) {
  940.     for ($j = 0; $j < $k; ++$j) {
  941. unshift (@{$colstack[$j]},
  942.  [$min_index,
  943.   $collected_values[$j]]);
  944. $collected_values[$j] = undef;
  945.     }
  946.     $min_index = $this_index;
  947. }
  948. if ($cmp <= 0) {
  949.     $collected_values[$k] = $pair->[1];
  950.     shift @{$colstack[$k]};
  951. }
  952.     }
  953. }
  954. ($base_index = undef), last
  955.     if !defined $min_index;
  956. last if defined $end && index_compare ($min_index, $end) >= 0;
  957. &$mapfn ($min_index, @collected_values);
  958. ++$call_counter;
  959. $base_index = $min_index;
  960.     }
  961. } else {
  962.     return undef;
  963. }
  964. last if !defined $base_index;
  965. last if defined $end and index_compare ($base_index, $end) >= 0;
  966.     }
  967.     $call_counter;
  968. }
  969. 1;