1package Smokeping::probes::CiscoRTTMonEchoICMP;
2
3=head1 301 Moved Permanently
4
5This is a Smokeping probe module. Please use the command
6
7C<smokeping -man Smokeping::probes::CiscoRTTMonEchoICMP>
8
9to view the documentation or the command
10
11C<smokeping -makepod Smokeping::probes::CiscoRTTMonEchoICMP>
12
13to generate the POD document.
14
15=cut
16
17use strict;
18use base qw(Smokeping::probes::basefork);
19use Symbol;
20use Carp;
21use BER;
22use SNMP_Session;
23use SNMP_util "0.97";
24use Smokeping::ciscoRttMonMIB "0.2";
25
26sub pod_hash {
27	my $e = "=";
28	return {
29		name => <<DOC,
30Smokeping::probes::CiscoRTTMonEchoICMP - Probe for SmokePing
31DOC
32		description => <<DOC,
33A probe for smokeping, which uses the ciscoRttMon MIB functionality ("Service Assurance Agent", "SAA") of Cisco IOS to measure ICMP echo ("ping") roundtrip times between a Cisco router and any IP address.
34DOC
35		notes => <<DOC,
36${e}head2 IOS VERSIONS
37
38It is highly recommended to use this probe with routers running IOS 12.0(3)T or higher and to test it on less critical routers first. I managed to crash a router with 12.0(9) quite consistently ( in IOS lingo 12.0(9) is older code than 12.0(3)T ). I did not observe crashes on higher IOS releases, but messages on the router like the one below, when multiple processes concurrently accessed the same router (this case was IOS 12.1(12b) ):
39
40Aug 20 07:30:14: %RTT-3-SemaphoreBadUnlock: %RTR: Attempt to unlock semaphore by wrong RTR process 70, locked by 78
41
42Aug 20 07:35:15: %RTT-3-SemaphoreInUse: %RTR: Could not obtain a lock for RTR. Process 80
43
44
45${e}head2 INSTALLATION
46
47To install this probe copy ciscoRttMonMIB.pm files to (\$SMOKEPINGINSTALLDIR)/lib/Smokeping and CiscoRTTMonEchoICMP.pm to (\$SMOKEPINGINSTALLDIR)/lib/Smokeping/probes. V0.97 or higher of Simon Leinen's SNMP_Session.pm is required.
48
49The router(s) must be configured to allow read/write SNMP access. Sufficient is:
50
51	snmp-server community RTTCommunity RW
52
53If you want to be a bit more restrictive with SNMP write access to the router, then consider configuring something like this
54
55	access-list 2 permit 10.37.3.5
56	snmp-server view RttMon ciscoRttMonMIB included
57	snmp-server community RTTCommunity view RttMon RW 2
58
59The above configuration grants SNMP read-write only to 10.37.3.5 (the smokeping host) and only to the ciscoRttMon MIB tree. The probe does not need access to SNMP variables outside the RttMon tree.
60DOC
61		bugs => <<DOC,
62The probe sends unnecessary pings, i.e. more than configured in the "pings" variable, because the RTTMon MIB only allows to set a total time for all pings in one measurement run (one "life"). Currently the probe sets the life duration to "pings"*5+3 seconds (5 secs is the ping timeout value hardcoded into this probe).
63DOC
64		see_also => <<DOC,
65L<http://oss.oetiker.ch/smokeping/>
66
67L<http://www.switch.ch/misc/leinen/snmp/perl/>
68
69The best source for background info on SAA is Cisco's documentation on L<http://www.cisco.com> and the CISCO-RTTMON-MIB documentation, which is available at:
70L<ftp://ftp.cisco.com/pub/mibs/v2/CISCO-RTTMON-MIB.my>
71DOC
72		authors => <<DOC,
73Joerg.Kummer at Roche.com
74DOC
75	}
76}
77
78my $pingtimeout = 5;
79
80sub new($$$)
81{
82    my $proto = shift;
83    my $class = ref($proto) || $proto;
84    my $self = $class->SUPER::new(@_);
85
86    # no need for this if we run as a cgi
87    unless ( $ENV{SERVER_SOFTWARE} ) {
88        $self->{pingfactor} = 1000;
89    };
90    return $self;
91}
92
93sub ProbeDesc($){
94    my $self = shift;
95    return "CiscoRTTMonEchoICMP";
96}
97
98sub pingone ($$) {
99    my $self = shift;
100    my $target = shift;
101
102    my $pings = $self->pings($target) || 20;
103    my $tos   = $target->{vars}{tos};
104    my $bytes = $target->{vars}{packetsize};
105
106    # use the process ID as as row number to make this poll distinct on the router;
107    my $row=$$;
108
109    if (defined
110        StartRttMibEcho($target->{vars}{ioshost}.":::::2", $target->{addr},
111        $bytes, $pings, $target->{vars}{iosint}, $tos, $row, $target->{vars}{vrf}))
112	{
113        # wait for the series to finish
114        sleep ($pings*$pingtimeout+5);
115        if (my @times=FillTimesFromHistoryTable($target->{vars}{ioshost}.":::::2", $pings, $row)){
116		DestroyData ($target->{vars}{ioshost}.":::::2", $row);
117		return @times;
118	   }
119	else {
120		return();
121		}
122	}
123    else {
124        return ();
125    }
126}
127
128sub StartRttMibEcho ($$$$$$){
129    my ($host, $target, $size, $pings, $sourceip, $tos, $row,$vrf) = @_;
130
131	# resolve the target name and encode its IP address
132	$_=$target;
133	if (!/^([0-9]|\.)+/) {
134		(my $name, my $aliases, my $addrtype, my $length, my @addrs) = gethostbyname ($target);
135		$target=join('.',(unpack("C4",$addrs[0])));
136		}
137	my @octets=split(/\./,$target);
138	my $encoded_target= pack ("CCCC", @octets);
139
140	# resolve the source name and encode its IP address
141	my $encoded_source = undef;
142	if (defined $sourceip) {
143		$_=$sourceip;
144		if (!/^([0-9]|\.)+/) {
145			(my $name, my $aliases, my $addrtype, my $length, my @addrs) = gethostbyname ($sourceip);
146			$sourceip=join('.',(unpack("C4",$addrs[0])));
147			}
148		my @octets=split(/\./,$sourceip);
149		$encoded_source= pack ("CCCC", @octets);
150		}
151
152	#############################################################
153	# rttMonCtrlAdminStatus - 1:active 2:notInService 3:notReady 4:createAndGo 5:createAndWait 6:destroy
154	#delete data from former measurements
155	#return undef unless defined
156	#  &snmpset($host, "rttMonCtrlAdminStatus.$row",'integer', 	6);
157
158	#############################################################
159	# Check RTTMon version and supported protocols
160    	$SNMP_Session::suppress_warnings = 10; # be silent
161	(my $version)=&snmpget ($host, "rttMonApplVersion");
162	if (! defined $version ) {
163		Smokeping::do_log ("$host doesn't support or allow RTTMon !\n");
164		return undef;
165	}
166	Smokeping::do_log ("$host supports $version\n");
167    	$SNMP_Session::suppress_warnings = 0; # report errors
168
169	# echo(1), pathEcho(2), fileIO(3), script(4), udpEcho(5), tcpConnect(6), http(7),
170	# dns(8), jitter(9), dlsw(10), dhcp(11), ftp(12)
171	my $udpEchoSupported=0==1;
172	snmpmaptable ($host,
173		sub () {
174			my ($proto, $supported) = @_;
175			# 1 is true , 2 is false
176			$udpEchoSupported=0==0 if ($proto==5 && $supported==1);
177		},
178		"rttMonApplSupportedRttTypesValid");
179
180	#############################################################
181	#setup the new data row
182
183	my @params=();
184	push @params,
185		"rttMonCtrlAdminStatus.$row",		'integer', 	5,
186		"rttMonCtrlAdminRttType.$row",		'integer',	1,
187		"rttMonEchoAdminProtocol.$row",		'integer',	2,
188		"rttMonEchoAdminTargetAddress.$row",	'octetstring',	$encoded_target,
189		"rttMonCtrlAdminTimeout.$row",		'integer',	$pingtimeout*1000,
190		"rttMonCtrlAdminFrequency.$row",	'integer',	$pingtimeout,
191		"rttMonHistoryAdminNumBuckets.$row",	'integer', 	$pings,
192		"rttMonHistoryAdminNumLives.$row",	'integer', 	1,
193		"rttMonHistoryAdminFilter.$row", 	'integer', 	2,
194		"rttMonEchoAdminPktDataRequestSize.$row",'integer',	$size-8,
195		"rttMonScheduleAdminRttStartTime.$row",	'timeticks',	1,
196		"rttMonScheduleAdminRttLife.$row",	'integer',	$pings*$pingtimeout+3,
197		"rttMonScheduleAdminConceptRowAgeout.$row",'integer', 	60;
198
199    if(defined($vrf)){
200        push @params,"rttMonEchoAdminVrfName.$row",'octetstring',$vrf;
201    }
202
203	# with udpEcho support (>= 12.0(3)T ) the ICMP ping support was enhanced in the RTTMon SW - we are
204	# NOT using udpEcho, but echo (ICMP echo, ping)
205	if ($udpEchoSupported) {
206		push @params, 	"rttMonEchoAdminTOS.$row",		'integer',	$tos;
207		push @params, 	"rttMonCtrlAdminNvgen.$row",		'integer',	2;
208
209		# the router (or this script) doesn't check whether the IP address is one of
210		# the router's IP address, i.e. the router might send packets, but never
211		# gets ping replies..
212		if (defined $sourceip) {
213			push @params, 	"rttMonEchoAdminSourceAddress.$row",		'octetstring',	$encoded_source;
214		}
215	}
216	else {
217		Smokeping::do_log ("Warning this host does not support ToS or iosint\n");
218	}
219
220	return undef unless defined
221	   &snmpset($host, @params);
222
223	#############################################################
224	# and go !
225	return undef unless defined
226	   &snmpset($host, "rttMonCtrlAdminStatus.$row",'integer',1);
227
228	return 1;
229}
230
231
232# RttResponseSense values
233# 1:ok 2:disconnected 3:overThreshold 4:timeout 5:busy 6:notConnected 7:dropped 8:sequenceError
234# 9:verifyError 10:applicationSpecific 11:dnsServerTimeout 12:tcpConnectTimeout 13:httpTransactionTimeout
235#14:dnsQueryError 15:httpError 16:error
236
237sub FillTimesFromHistoryTable($$$$) {
238	my ($host, $pings, $row) = @_;
239	my @times;
240
241	# snmpmaptable walks two tables (of equal size)
242	# - "rttMonHistoryCollectionCompletionTime.$row",
243	# - "rttMonHistoryCollectionSense.$row"
244	# The code in the sub() argument is executed for each index value snmptable walks
245	snmpmaptable ($host,
246		sub () {
247			my ($index, $rtt, $status) = @_;
248			push @times, (sprintf ("%.10e", $rtt/1000))
249				if ($status==1);
250		},
251		"rttMonHistoryCollectionCompletionTime.$row",
252		"rttMonHistoryCollectionSense.$row");
253
254	return sort { $a <=> $b } @times;
255}
256
257sub DestroyData ($$) {
258	my ($host, $row) = @_;
259
260	&snmpset($host, "rttMonCtrlOperState.$row",             'integer',      3);
261	&snmpset($host, "rttMonCtrlAdminStatus.$row",           'integer',      2);
262	#delete any old config
263	&snmpset($host, "rttMonCtrlAdminStatus.$row",           'integer',      6);
264}
265
266sub targetvars {
267	my $class = shift;
268	return $class->_makevars($class->SUPER::targetvars, {
269		_mandatory => [ 'ioshost' ],
270		ioshost => {
271			_example => 'RTTcommunity@Myrouter.foobar.com.au',
272			_doc => <<DOC,
273The (mandatory) ioshost parameter specifies the Cisco router, which will
274execute the pings, as well as the SNMP community string on the router.
275DOC
276		},
277        timeout => {
278             _re => '\d+',
279             _example => 15,
280             _default => $pingtimeout+10,
281             _doc => "How long a single RTTMonEcho ICMP 'ping' take at maximum plus 10 seconds to spare. Since we control our own timeout the only purpose of this is to not have us killed by the ping method from basefork.",
282        },
283		iosint => {
284			_example => '10.33.22.11',
285			_doc => <<DOC,
286The (optional) iosint parameter is the source address for the pings
287sent. This should be one of the active (!) IP addresses of the router to
288get results. IOS looks up the target host address in the forwarding table
289and then uses the interface(s) listed there to send the ping packets. By
290default IOS uses the (primary) IP address on the sending interface as
291source address for a ping. The RTTMon MIB versions before IOS 12.0(3)T
292didn't support this parameter.
293DOC
294		},
295		tos => {
296			_example => 160,
297			_default => 0,
298			_doc => <<DOC,
299The (optional) tos parameter specifies the value of the ToS byte in
300the IP header of the pings. Multiply DSCP values times 4 and Precedence
301values times 32 to calculate the ToS values to configure, e.g. ToS 160
302corresponds to a DSCP value 40 and a Precedence value of 5. The RTTMon
303MIB versions before IOS 12.0(3)T didn't support this parameter.
304DOC
305            },
306        vrf => {
307            _example => "INTERNET",
308            _doc => <<DOC,
309The the VPN name in which the RTT operation will be used. For regular RTT
310operation this field should not be configured. The agent
311will use this field to identify the VPN routing Table for
312this operation.
313DOC
314		},
315		packetsize => {
316			_doc => <<DOC,
317The packetsize parameter lets you configure the packetsize for the pings
318sent. The minimum is 8, the maximum 16392. Use the same number as with
319fping, if you want the same packet sizes being used on the network.
320DOC
321			_default => 56,
322			_re => '\d+',
323			_sub => sub {
324				my $val = shift;
325				return "ERROR: packetsize must be between 8 and 16392"
326					unless $val >= 8 and $val <= 16392;
327				return undef;
328            }
329        }
330	});
331}
332
3331;
334
335