diff options
-rw-r--r-- | lib/Smokeping/pingMIB.pm | 80 | ||||
-rw-r--r-- | lib/Smokeping/probes/DismanPing.pm | 456 |
2 files changed, 536 insertions, 0 deletions
diff --git a/lib/Smokeping/pingMIB.pm b/lib/Smokeping/pingMIB.pm new file mode 100644 index 0000000..57918ab --- /dev/null +++ b/lib/Smokeping/pingMIB.pm @@ -0,0 +1,80 @@ +# +# +# a few variable definitions to use pingMIB +# +# Bill Fenner, 10/23/06 +# Based on ciscoRttMonMIB.pm +# + +package Smokeping::pingMIB; + +require 5.004; + +use vars qw($VERSION); +use Exporter; + +use BER; +use SNMP_Session; +use SNMP_util "0.89"; + +$VERSION = '0.1'; + +@ISA = qw(Exporter); + +sub version () { $VERSION; }; + +# Scalars: +snmpmapOID("pingMaxConcurrentRequests", "1.3.6.1.2.1.80.1.1.0"); + +# pingCtlTable +snmpmapOID("pingCtlOwnerIndex", "1.3.6.1.2.1.80.1.2.1.1"); +snmpmapOID("pingCtlTestName", "1.3.6.1.2.1.80.1.2.1.2"); +snmpmapOID("pingCtlTargetAddressType", "1.3.6.1.2.1.80.1.2.1.3"); +snmpmapOID("pingCtlTargetAddress", "1.3.6.1.2.1.80.1.2.1.4"); +snmpmapOID("pingCtlDataSize", "1.3.6.1.2.1.80.1.2.1.5"); +snmpmapOID("pingCtlTimeOut", "1.3.6.1.2.1.80.1.2.1.6"); +snmpmapOID("pingCtlProbeCount", "1.3.6.1.2.1.80.1.2.1.7"); +snmpmapOID("pingCtlAdminStatus", "1.3.6.1.2.1.80.1.2.1.8"); +snmpmapOID("pingCtlDataFill", "1.3.6.1.2.1.80.1.2.1.9"); +snmpmapOID("pingCtlFrequency", "1.3.6.1.2.1.80.1.2.1.10"); +snmpmapOID("pingCtlMaxRows", "1.3.6.1.2.1.80.1.2.1.11"); +snmpmapOID("pingCtlStorageType", "1.3.6.1.2.1.80.1.2.1.12"); +snmpmapOID("pingCtlTrapGeneration", "1.3.6.1.2.1.80.1.2.1.13"); +snmpmapOID("pingCtlTrapProbeFailureFilter", "1.3.6.1.2.1.80.1.2.1.14"); +snmpmapOID("pingCtlTrapTestFailureFilter", "1.3.6.1.2.1.80.1.2.1.15"); +snmpmapOID("pingCtlType", "1.3.6.1.2.1.80.1.2.1.16"); +snmpmapOID("pingCtlDescr", "1.3.6.1.2.1.80.1.2.1.17"); +snmpmapOID("pingCtlSourceAddressType", "1.3.6.1.2.1.80.1.2.1.18"); +snmpmapOID("pingCtlSourceAddress", "1.3.6.1.2.1.80.1.2.1.19"); +snmpmapOID("pingCtlIfIndex", "1.3.6.1.2.1.80.1.2.1.20"); +snmpmapOID("pingCtlByPassRouteTable", "1.3.6.1.2.1.80.1.2.1.21"); +snmpmapOID("pingCtlDSField", "1.3.6.1.2.1.80.1.2.1.22"); +snmpmapOID("pingCtlRowStatus", "1.3.6.1.2.1.80.1.2.1.23"); + +# pingResultsTable +snmpmapOID("pingResultsOperStatus", "1.3.6.1.2.1.80.1.3.1.1"); +snmpmapOID("pingResultsIpTargetAddressType", "1.3.6.1.2.1.80.1.3.1.2"); +snmpmapOID("pingResultsIpTargetAddress", "1.3.6.1.2.1.80.1.3.1.3"); +snmpmapOID("pingResultsMinRtt", "1.3.6.1.2.1.80.1.3.1.4"); +snmpmapOID("pingResultsMaxRtt", "1.3.6.1.2.1.80.1.3.1.5"); +snmpmapOID("pingResultsAverageRtt", "1.3.6.1.2.1.80.1.3.1.6"); +snmpmapOID("pingResultsProbeResponses", "1.3.6.1.2.1.80.1.3.1.7"); +snmpmapOID("pingResultsSentProbes", "1.3.6.1.2.1.80.1.3.1.8"); +snmpmapOID("pingResultsRttSumOfSquares", "1.3.6.1.2.1.80.1.3.1.9"); +snmpmapOID("pingResultsLastGoodProbe", "1.3.6.1.2.1.80.1.3.1.10"); + +# pingProbeHistoryTable +snmpmapOID("pingProbeHistoryIndex", "1.3.6.1.2.1.80.1.4.1.1"); +snmpmapOID("pingProbeHistoryResponse", "1.3.6.1.2.1.80.1.4.1.2"); +snmpmapOID("pingProbeHistoryStatus", "1.3.6.1.2.1.80.1.4.1.3"); +snmpmapOID("pingProbeHistoryLastRC", "1.3.6.1.2.1.80.1.4.1.4"); +snmpmapOID("pingProbeHistoryTime", "1.3.6.1.2.1.80.1.4.1.5"); + +# pingImplementationTypeDomains - if we end up supporting other ping types +snmpmapOID("pingIcmpEcho", "1.3.6.1.2.1.80.3.1"); +snmpmapOID("pingUdpEcho", "1.3.6.1.2.1.80.3.2"); +snmpmapOID("pingSnmpQuery", "1.3.6.1.2.1.80.3.3"); +snmpmapOID("pingTcpConnectionAttempt", "1.3.6.1.2.1.80.3.4"); + +# return 1 to indicate that all is ok.. +1; diff --git a/lib/Smokeping/probes/DismanPing.pm b/lib/Smokeping/probes/DismanPing.pm new file mode 100644 index 0000000..05c54a7 --- /dev/null +++ b/lib/Smokeping/probes/DismanPing.pm @@ -0,0 +1,456 @@ +package Smokeping::probes::DismanPing; + +=head1 301 Moved Permanently + +This is a Smokeping probe module. Please use the command + +C<smokeping -man Smokeping::probes::DismanPing> + +to view the documentation or the command + +C<smokeping -makepod Smokeping::probes::DismanPing> + +to generate the POD document. + +=cut + +use strict; +use base qw(Smokeping::probes::basevars); +use SNMP_Session; +use SNMP_util "0.89"; +use Smokeping::pingMIB "0.1"; +use Socket; + +sub pod_hash { + my $e = "="; + return { + name => <<DOC, +Smokeping::probes::DismanPing - DISMAN-PING-MIB Probe for SmokePing +DOC + description => <<DOC, +Uses the DISMAN-PING-MIB to cause a remote system to send probes. +DOC + authors => <<DOC, +Bill Fenner <fenner\@research.att.com> +DOC + credits => <<DOC, +This structure of this probe module is heavily based on +L<Smokeping::probes::CiscoRTTMonEchoICMP|Smokeping::probes::CiscoRTTMonEchoICMP> +by Joerg.Kummer at Roche.com. +DOC + notes => <<DOC, +${e}head2 MENU NAMES + +This probe uses the menu name of a test as part of the unique +index. If the menu name is longer than 32 characters, the last +32 characters are used for the index. Collisions are *B<not>* +detected and simply cause one test's results to be used for +all colliding names. + +${e}head2 CONFIGURATION + +This probe requires read/write access to the pingCtlTable. +It also requires read-only access to the pingResultsTable and the +pingHistoryTable. The DISMAN-PING-MIB is structured such that +it is possible to restrict by pingCtlOwnerIndex. This probe +uses a pingCtlOwnerIndex of "smokeping" by default; use +B<ownerindex> to configure this if needed. + +${e}head2 SAMPLE JUNOS CONFIGURATION + +This configuration permits the community "pinger" read-write +access to the full DISMAN-PING-MIB, but only when sourced +from the manager at B<192.0.2.134>. + + snmp { + view pingMIB { + oid .1.3.6.1.2.1.80 include; + } + community pinger { + view pingMIB; + authorization read-write; + clients { + 192.0.2.134/32; + } + } + } + +${e}head2 SAMPLE CONFIGURATIONS NOTE + +This configuration allows the "pinger" community full access to the +DISMAN-PING-MIB. There is information in the description of +B<pingCtlOwnerIndex> in RFC 4560 (L<http://tools.ietf.org/html/rfc4560>) +about using the vacmViewTreeFamilyTable to further restrict access. +The author has not tried this method. +DOC + + #${e}head2 SAMPLE IOS CONFIGURATION + # + #Note: I have no clue if IOS supports DISMAN-PING-MIB. + # + # access-list 2 permit 192.0.2.134 + # snmp-server view pingMIB .1.3.6.1.2.1.80 included + # snmp-server community pinger view pingMIB RW 2 + # + }; +} + +sub probevars { + my $class = shift; + + # This is structured a little differently than the + # average probe. _makevars prefers values from the + # first argument, but we have to override the superclass's + # pings value. So, we put our values in the first argument. + # However, _makevars modifies its second argument, and we + # don't want to modify the superclass's value, so we + # make a copy in $tmp. + my $tmp = { %{ $class->SUPER::probevars } }; + return $class->_makevars( + { + pings => { + _re => '\d+', + _default => 15, + _example => 15, + _sub => sub { + my $val = shift; + return + "ERROR: for DismanPing, pings must be between 1 and 15" + unless $val >= 1 and $val <= 15; + return undef; + }, + _doc => <<DOC, +How many pings should be sent to each target. Note that the maximum value +for DismanPing is 15, which is less than the SmokePing default, so this +class has its own default value. If your Database section specifies a +value less than 15, you must also set it for this probe. +Note that the number of pings in +the RRD files is fixed when they are originally generated, and if you +change this parameter afterwards, you'll have to delete the old RRD +files or somehow convert them. +DOC + }, + }, + $tmp + ); +} + +sub targetvars { + my $class = shift; + return $class->_makevars( + $class->SUPER::targetvars, + { + _mandatory => ['pinghost'], + ownerindex => { + _doc => <<DOC, +The SNMP OwnerIndex to use when setting up the test. +When using VACM, can map to a Security Name or Group Name +of the entity running the test. +DOC + _example => "smokeping" + }, + pinghost => { + _example => 'pinger@router.example.com', + _doc => <<DOC, +The (mandatory) pinghost parameter specifies the remote system which will +execute the pings, as well as the SNMP community string on the device. +DOC + }, + pingsrc => { + _example => '192.0.2.9', + _doc => <<DOC, +The (optional) pingsrc parameter specifies the source address to be used +for pings. If specified, this parameter must identify an IP address +assigned to pinghost. +DOC + }, + + # tos => { + # _example => 160, + # _default => 0, + # _doc => <<DOC, + #The (optional) tos parameter specifies the value of the ToS byte in + #the IP header of the pings. Multiply DSCP values times 4 and Precedence + #values times 32 to calculate the ToS values to configure, e.g. ToS 160 + #corresponds to a DSCP value 40 and a Precedence value of 5. + #DOC + # }, + packetsize => { + _doc => <<DOC, +The packetsize parameter lets you configure the packet size for the pings +sent. The minimum is 8, the maximum 65507. Use the same number as with +fping if you want the same packet sizes being used on the network. +DOC + _default => 56, + _re => '\d+', + _sub => sub { + my $val = shift; + return "ERROR: packetsize must be between 8 and 65507" + unless $val >= 8 and $val <= 65507; + return undef; + } + }, + } + ); +} + +# XXX +# This is copied from basefork.pm; it actually belongs +# in basevars.pm. +sub pod_variables { + my $class = shift; + my $pod = $class->SUPER::pod_variables; + my $targetvars = $class->targetvars; + $pod .= "Supported target-specific variables:\n\n"; + $pod .= $class->_pod_variables($targetvars); + return $pod; +} + +sub new($$$) { + my $proto = shift; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new(@_); + + # no need for this if we run as a cgi + unless ( $ENV{SERVER_SOFTWARE} ) { + + # Initialization stuff that might take time + } + return $self; +} + +sub ProbeDesc($) { + my $self = shift; + my $bytes = $self->{properties}{packetsize} || 56; + return "DISMAN ICMP Echo Pings ($bytes Bytes)"; +} + +# RFC 4560: +# A single SNMP PDU can be used to create and start a remote +# ping test. Within the PDU, pingCtlTargetAddress should be set to the +# target host's address (pingCtlTargetAddressType will default to +# ipv4(1)), pingCtlAdminStatus to enabled(1), and pingCtlRowStatus to +# createAndGo(4). +# +# At least one implementation doesn't implement a default for +# pingCtlTargetAddressType (and the MIB itself doesn't specify +# such a default) +# +# Philosophically, I'd like to just leave the row there and +# re-enable the test if the row is there - but there's no easy +# way to verify that the values haven't changed since the last +# time we set it. +sub ping($) { + use Data::Dumper; + my $self = shift; + my $pending = {}; + my $longest = 0; + my $start = time; + + foreach my $t ( @{ $self->targets } ) { + my $addr = $t->{addr}; + my $idx = idx($t); + my $host = host($t); + + # Delete any existing row. Ignore error. + #Smokeping::do_log("DismanPing deleting for $host $t->{vars}{menu}"); + my $ret = + &snmpset( $host, "pingCtlRowStatus.$idx", "integer", 6 ); #destroy + if ( !defined($ret) ) { + Smokeping::do_log( +"DismanPing: old probe for $t->{vars}{menu} is probably running: " + . $SNMP_Session::errmsg ); + next; + } + + my $targetaddr = inet_aton($addr); + if ( !defined($targetaddr) ) { + Smokeping::do_log( +"DismanPing can't resolve destination address $addr for $t->{vars}{menu}" + ); + next; + } + + #XXX consider ipv6 - esp. what does inet_aton() return + #XXX todo: test failure handling code by setting ProbeCOunt and MaxRows + # differently than pings + my @values = ( + "pingCtlTargetAddressType.$idx", "integer", 1, #ipv4 + "pingCtlTargetAddress.$idx", "octetstring", $targetaddr, + "pingCtlProbeCount.$idx", "gauge", $t->{vars}{pings}, + "pingCtlMaxRows.$idx", "gauge", $t->{vars}{pings}, + "pingCtlAdminStatus.$idx", "integer", 1, #enabled + "pingCtlRowStatus.$idx", "integer", 4, #createAndGo + +# "pingCtlRowStatus.$idx", "integer", 5, #createAndWait + ); + + # add pingsrc, packetsize into @values if defined + if ( defined( $t->{vars}{packetsize} ) ) { + unshift( @values, + "pingCtlDataSize.$idx", "gauge", $t->{vars}{packetsize} ); + } + if ( defined( $t->{vars}{pingsrc} ) ) { + my $srcaddr = inet_aton( $t->{vars}{pingsrc} ); + if ( !defined($srcaddr) ) { + Smokeping::do_log( +"WARNING: DismanPing can't resolve source address $t->{vars}{pingsrc} for $t->{vars}{menu}" + ); + } + else { + unshift( + @values, + "pingCtlSourceAddressType.$idx", "integer", 1, #ipv4 + "pingCtlSourceAddress.$idx", "octetstring", $srcaddr + ); + } + } + + # Todo: pingCtlDSField. + # Todo: pingCtlTimeout. + my @snmpsetret; + if ( ( @snmpsetret = &snmpset( $host, @values ) ) + && defined( $snmpsetret[0] ) ) + { + { + use Data::Dumper; + open( my $tmp, ">>/tmp/disman-smoke" ); + print $tmp Dumper( + scalar( localtime(time) ), + $t->{vars}{menu}, + \@snmpsetret + ); + close($tmp); + } + $pending->{ $t->{tree} } = 1; + } + else { + Smokeping::do_log( +"ERROR: DismanPing row creation failed for $t->{vars}{menu}: $SNMP_Session::errmsg" + ); + } + my $timeout = 3; # XXX DEFVAL for pingCtlTimeOut + my $length = $t->{vars}{pings} * $timeout; + if ( $length > $longest ) { + $longest = $length; + } + } + my $setup = time - $start; + Smokeping::do_debuglog( + "DismanPing took $setup to set up, now sleeping for $longest"); + sleep($longest); + my $allok = 0; + my $startend = time; + while ( !$allok ) { + $allok = 1; + foreach my $t ( @{ $self->targets } ) { + next unless ( $pending->{ $t->{tree} } ); + my $idx = idx($t); + my $host = host($t); + + # check if it's done - pingResultsOperStatus != 1 + my $status = &snmpget( $host, "pingResultsOperStatus.$idx" ); + if ( !defined($status) || $status == 1 ) + { # if SNMP fails, assume it's not done. + my $howlong = time - $start; + if ( $howlong > $self->step ) { + Smokeping::do_log( +"DismanPing: abandoning $t->{vars}{menu} after $howlong seconds" + ); + $pending->{ $t->{tree} } = 0; + } + else { + Smokeping::do_log( +"DismanPing: $t->{vars}{menu} is still running after $howlong seconds" + ); + $allok = 0; + } + next; + } + + # if so, get results from History Table + my @times = (); + + # TODO: log message if you have a bad status other than TimedOut + my $ret = snmpmaptable( + $host, + sub() { + my ( $index, $rtt, $status ) = @_; + push @times, [ sprintf( "%.10e", $rtt / 1000 ), $status ]; + }, + "pingProbeHistoryResponse.$idx", + "pingProbeHistoryStatus.$idx" + ); + Smokeping::do_debuglog( "DismanPing: table download returned " + . ( defined($ret) ? $ret : "undef" ) ); + + # Make sure we have exactly pings results. + # Fewer are probably an implementation problem (we asked for + # 15, it said the test was done, but didn't return 15). + # More are a less-bad implementation problem - we can keep + # the last 15. + if ( @times < $t->{vars}{pings} ) { + Smokeping::do_log( "DismanPing: $t->{vars}{menu} only returned " + . scalar(@times) + . " results" ); + @times = (); + } + elsif ( @times > $t->{vars}{pings} ) { + Smokeping::do_log( "DismanPing: $t->{vars}{menu} returned " + . scalar(@times) + . " results, taking last $t->{vars}{pings}" ); + @times = @times[ $#times - $t->{vars}{pings} .. $#times ]; + } + if (@times) { + my (@goodtimes) = (); + foreach $t (@times) { + push( @goodtimes, $t->[0] ) + if ( $t->[1] == 1 ); # responseReceived(1) + } + $self->{rtts}{ $t->{tree} } = [ sort { $a <=> $b } @goodtimes ]; + } + $pending->{ $t->{tree} } = 0; + } + sleep 5 unless ($allok); + } + my $howlong = time - $start; + my $endtime = time - $startend; + Smokeping::do_debuglog( + "DismanPing took $howlong total, $endtime collecting results"); +} + +# Return index string for this test: +# INDEX { +# pingCtlOwnerIndex, +# pingCtlTestName +# } +# This is the full index for pingCtlTable and +# pingResultsTable, and the prefix of the index for +# pingProbeHistoryTable. +# +# Uses the last 32 characters of menu name to +# get a unique test name. +sub idx ($) { + my $t = shift; + + my $ownerindex = $t->{vars}{ownerindex} || "smokeping"; + my $testname = $t->{vars}{menu}; + if ( length($testname) > 32 ) { + $testname = substr( $testname, -32 ); + } + return join( ".", + length($ownerindex), unpack( "C*", $ownerindex ), + length($testname), unpack( "C*", $testname ) ); +} + +sub host ($) { + my $t = shift; + + # gotta be aggressive with the SNMP to keep within + # the time budget, so set the timeout to 1 second + # and only try twice. + # hostname:port:timeout:retries:backoff:version + return $t->{vars}{pinghost} . "::1:2::2"; +} + +1; |