summaryrefslogtreecommitdiffstats
path: root/lib/Smokeping
diff options
context:
space:
mode:
authorTobi Oetiker <tobi@oetiker.ch>2011-08-23 15:58:56 +0200
committerTobi Oetiker <tobi@oetiker.ch>2011-08-23 15:58:56 +0200
commita1fbf832f9f0ba3043c3300aa0ca3a3d841ce41c (patch)
treee87cf79fbda0b1535ceec39af1619cf7258c1881 /lib/Smokeping
parent7bcda72c21f3d1d28760427455b3b1c9b2f48b44 (diff)
downloadsmokeping-a1fbf832f9f0ba3043c3300aa0ca3a3d841ce41c.tar.gz
smokeping-a1fbf832f9f0ba3043c3300aa0ca3a3d841ce41c.tar.xz
started integration of DismanPing support with modules from Bill Fenners: Bill's Permanently Unfinished but Potentially Useful scripts
http://code.google.com/p/pupu/ --- thanks bill!
Diffstat (limited to 'lib/Smokeping')
-rw-r--r--lib/Smokeping/pingMIB.pm80
-rw-r--r--lib/Smokeping/probes/DismanPing.pm456
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;