1#!/usr/bin/perl -w 2use strict; 3use Test; 4BEGIN { plan tests => 86 } 5# Copyright (c) 2003-2004 AirWave Wireless, Inc. 6 7# Redistribution and use in source and binary forms, with or without 8# modification, are permitted provided that the following conditions 9# are met: 10 11# 1. Redistributions of source code must retain the above 12# copyright notice, this list of conditions and the following 13# disclaimer. 14# 2. Redistributions in binary form must reproduce the above 15# copyright notice, this list of conditions and the following 16# disclaimer in the documentation and/or other materials provided 17# with the distribution. 18# 3. The name of the author may not be used to endorse or 19# promote products derived from this software without specific 20# prior written permission. 21 22# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS 23# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 24# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 26# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 28# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 29# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 30# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 31# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 32# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33use lib '..'; 34use Carp qw(confess); 35use NSNMP; 36use POSIX; 37use IO::Socket; 38use NSNMP::Agent; 39 40$SIG{__DIE__} = sub { confess @_ }; 41 42my $encoded_oid = NSNMP->encode_oid('.1.2.3.4.5'); 43ok($encoded_oid); 44 45my $request_id = 'fwsa'; 46sub getreq { 47 my ($oid) = @_; 48 return NSNMP->encode( 49 type => NSNMP::GET_REQUEST, 50 varbindlist => [[NSNMP->encode_oid($oid), NSNMP::NULL, '']], 51 request_id => $request_id, 52 ); 53} 54 55my $initial_request = getreq('.1.2.3.4.5'); 56ok($initial_request); 57ok(NSNMP->decode($initial_request)); 58 59 60my $sysname = '.1.3.6.1.2.1.1.5'; 61my $sysname0 = "$sysname.0"; 62 63# noSuchName 64{ 65 my $agent = NSNMP::Agent->new(types => { }, values => { }); 66 ok($agent); 67 my $response = $agent->handle_request($initial_request); 68 ok($response); 69 my $dr = NSNMP->decode($response); 70 ok($dr); 71 ok($dr->error_status, NSNMP::noSuchName); 72 ok($dr->error_index, 1); 73 ok($dr->version, 1); 74 ok($dr->type, NSNMP::GET_RESPONSE); 75 ok($dr->request_id, $request_id); 76 ok($dr->community, 'public'); 77 my @varbinds = $dr->varbindlist; 78 ok(@varbinds, 1); 79 ok($varbinds[0][0], $encoded_oid); 80 ok($varbinds[0][1], NSNMP::NULL); # ??? 81 ok($varbinds[0][2], ''); 82} 83 84# fetching an existing name 85{ 86 my $agent = NSNMP::Agent->new( 87 types => { '.1.3.6.1.2.1.1.5' => NSNMP::OCTET_STRING }, 88 values => { $sysname0 => 'thor.cs.cmu.edu' }, 89 ); 90 91 my $response = $agent->handle_request($initial_request); 92 ok($response); 93 ok(NSNMP->decode($response)->error_status, NSNMP::noSuchName); 94 95 $response = $agent->handle_request(getreq($sysname0)); 96 97 ok($response); 98 my $dr = NSNMP->decode($response); 99 ok($dr); 100 ok($dr->error_status, 0); 101 ok($dr->error_index, 0); 102 ok($dr->version, 1); 103 ok($dr->type, NSNMP::GET_RESPONSE); 104 ok($dr->request_id, $request_id); 105 ok($dr->community, 'public'); 106 my @varbinds = $dr->varbindlist; 107 ok(@varbinds, 1); 108 ok($varbinds[0][0], NSNMP->encode_oid($sysname0)); 109 ok($varbinds[0][1], NSNMP::OCTET_STRING); 110 ok($varbinds[0][2], 'thor.cs.cmu.edu'); 111} 112 113# handling of wrong community string 114{ 115 my $agent = NSNMP::Agent->new(types => {}, values => {}); 116 my %request_args = ( 117 type => NSNMP::GET_REQUEST, 118 request_id => 'pets', 119 varbindlist => [[NSNMP->encode_oid($sysname0), NSNMP::NULL, '']], 120 ); 121 my $response = $agent->handle_request(NSNMP->encode(%request_args)); 122 ok($response); 123 my $decoded = NSNMP->decode($response); 124 ok($decoded->error_status, NSNMP::noSuchName); 125 126 $response = $agent->handle_request(NSNMP->encode(%request_args, 127 community => 'cilbup', 128 )); 129 ok($response, undef); 130 131 # non-default community string 132 $agent = NSNMP::Agent->new(types => {}, values => {}, community => 'cilbup'); 133 ok($agent->handle_request(NSNMP->encode(%request_args)), undef); 134 ok($agent->handle_request(NSNMP->encode( 135 %request_args, community => 'cilbup', 136 ))); 137} 138 139# handling network packets 140{ 141 # We'd like to use NSNMP::Simple here, but the problem is that we use 142 # NSNMP::Agent in NSNMP::Simple's tests, so if NSNMP::Simple's tests 143 # start failing, we want to be able to use these tests to see if the 144 # problem is with NSNMP::Simple or NSNMP::Agent. 145 146 # receive if a packet happens soon, returning a timedout boolean and the packet 147 sub recv_timeout { 148 my ($socket) = @_; 149 my $rin = ''; 150 vec($rin, fileno($socket), 1) = 1; 151 select($rin, undef, undef, .25); # PLENTY of time to get a response 152 if (vec($rin, fileno($socket), 1)) { 153 my $recv; 154 if (recv $socket, $recv, 65536, 0) { 155 return (0, $recv); 156 } # else there was an error, e.g. ECONNREFUSED, so fall through 157 } 158 return 1, undef; 159 } 160 161 my $agent = NSNMP::Agent->new( 162 types => { '.1.3.6.1.2.1.1.5' => NSNMP::OCTET_STRING }, 163 values => { $sysname0 => 'thor.cs.cmu.edu' }, 164 ); 165 166 my $port = 16161; 167 my $pid = $agent->spawn($port); 168 END { exit(0) } # for some reason, the other END block results in exiting with status 9 169 END { if ($pid) { kill(9, $pid); wait(); } } # clean up 170 171 my $talksocket = IO::Socket::INET->new( 172 PeerAddr => "127.0.0.1:$port", 173 Proto => 'udp', 174 ); 175 $talksocket->send($initial_request); 176 my ($timedout, $message) = recv_timeout($talksocket); 177 ok(not $timedout); 178 ok($message); 179 180 my $dm = NSNMP->decode($message); 181 ok($dm->error_status, NSNMP::noSuchName); 182 ok($dm->error_index, 1); 183 184 $talksocket->send(NSNMP->encode( 185 type => NSNMP::GET_REQUEST, 186 request_id => 'BeaM', 187 varbindlist => [[NSNMP->encode_oid($sysname0), NSNMP::NULL, '']], 188 )); 189 ($timedout, $message) = recv_timeout($talksocket); 190 ok(not $timedout); 191 ok($message); 192 $dm = NSNMP->decode($message); 193 my @varbinds = $dm->varbindlist; 194 ok(@varbinds, 1); 195 ok(NSNMP->decode_oid($varbinds[0][0]), '1.3.6.1.2.1.1.5.0'); 196 ok($varbinds[0][1], NSNMP::OCTET_STRING); 197 ok($varbinds[0][2], 'thor.cs.cmu.edu'); 198 199 # wrong community string 200 $talksocket->send(NSNMP->encode( 201 type => NSNMP::GET_REQUEST, 202 request_id => 'BeaM', 203 varbindlist => [[NSNMP->encode_oid($sysname0), NSNMP::NULL, '']], 204 community => 'cilbup', 205 )); 206 ($timedout, $message) = recv_timeout($talksocket); 207 ok($timedout); 208 ok($message, undef); 209 210 # garbage packet 211 $talksocket->send('This does not look much like an SNMP packet'); 212 $talksocket->send($initial_request); 213 ($timedout, $message) = recv_timeout($talksocket); 214 ok(not $timedout); # garbage message didn't kill it 215 ok($message); 216 # ensure the reply was for the real SNMP message: 217 ok(NSNMP->decode($message)->request_id, $request_id); 218} 219 220# handling of set requests 221{ 222 my $agent = NSNMP::Agent->new( 223 types => { '.1.3.6.1.2.1.1.5' => NSNMP::OCTET_STRING }, 224 values => { $sysname0 => 'thor.cs.cmu.edu' }, 225 ); 226 my $get_request = NSNMP->encode( 227 type => NSNMP::GET_REQUEST, 228 request_id => 'love', 229 varbindlist => [[NSNMP->encode_oid($sysname0), NSNMP::NULL, '']], 230 ); 231 232 # first get the value 233 my $response = $agent->handle_request($get_request); 234 ok($response); 235 my $dr = NSNMP->decode($response); 236 ok($dr); 237 ok(($dr->varbindlist)[0]->[2], 'thor.cs.cmu.edu'); 238 239 # then set it to something else 240 $response = $agent->handle_request(NSNMP->encode( 241 type => NSNMP::SET_REQUEST, 242 request_id => 'kiss', 243 varbindlist => [[NSNMP->encode_oid($sysname0), NSNMP::OCTET_STRING, 244 'steadfast.canonical.org']], 245 )); 246 ok($response); 247 $dr = NSNMP->decode($response); 248 ok($dr); 249 ok($dr->error_status, 0); 250 ok($dr->error_index, 0); 251 ok($dr->request_id, 'kiss'); 252 ok(($dr->varbindlist)[0]->[2], 'steadfast.canonical.org'); 253 254 # then get it again and verify that it's changed 255 $response = $agent->handle_request($get_request); 256 ok($response); 257 $dr = NSNMP->decode($response); 258 ok($dr); 259 ok(($dr->varbindlist)[0]->[2], 'steadfast.canonical.org'); 260} 261 262# The Powerful Get-Next Operator 263{ 264 my $ifname = '.1.3.6.1.2.1.2.2.1.2'; 265 my %ifnames = ( 266 "$ifname.0" => 'lo', 267 "$ifname.10" => 'eth0', 268 "$ifname.2" => 'br0', 269 "$ifname.129" => 'big0', 270 ); 271 my $agent = NSNMP::Agent->new( 272 types => { $ifname => NSNMP::OCTET_STRING }, 273 values => \%ifnames, 274 ); 275 sub getnextreq { 276 my ($oid) = @_; 277 return NSNMP->encode(type => NSNMP::GET_NEXT_REQUEST, 278 request_id => 'fjew', 279 varbindlist => [[NSNMP->encode_oid($oid), NSNMP::NULL, '']], 280 ); 281 } 282 283 # child 284 my $response = NSNMP->decode($agent->handle_request(getnextreq($ifname))); 285 ok($response->request_id, 'fjew'); 286 ok($response->type, NSNMP::GET_RESPONSE); 287 ok($response->error_status, 0); 288 ok($response->error_index, 0); 289 my @varbinds = $response->varbindlist; 290 ok(@varbinds, 1); 291 ok($varbinds[0][0], NSNMP->encode_oid("$ifname.0")); 292 ok($varbinds[0][2], 'lo'); 293 294 # sibling 295 $response = NSNMP->decode($agent->handle_request(getnextreq("$ifname.0"))); 296 ok($response->error_status, 0); 297 @varbinds = $response->varbindlist; 298 ok($varbinds[0][0], NSNMP->encode_oid("$ifname.2")); 299 ok($varbinds[0][2], 'br0'); 300 301 # correct order 302 $response = NSNMP->decode($agent->handle_request(getnextreq("$ifname.2"))); 303 ok($response->error_status, 0); 304 ok(($response->varbindlist)[0]->[0], NSNMP->encode_oid("$ifname.10")); 305 306 # even for numbers over 128 307 $response = NSNMP->decode($agent->handle_request(getnextreq("$ifname.10"))); 308 ok($response->error_status, 0); 309 ok(($response->varbindlist)[0]->[0], NSNMP->encode_oid("$ifname.129")); 310 311 # and last OID 312 $response = NSNMP->decode($agent->handle_request(getnextreq("$ifname.129"))); 313 ok($response->error_status, NSNMP::noSuchName); 314} 315 316sub setreq { 317 my ($oid, $type, $value) = @_; 318 return NSNMP->encode( 319 type => NSNMP::SET_REQUEST, 320 varbindlist => [[NSNMP->encode_oid($oid), $type, $value]], 321 request_id => $request_id, 322 ); 323} 324 325# handling of different types 326{ 327 my $counter_oid = '.1.3.6.1.2.1.4.3'; 328 my $agent = NSNMP::Agent->new( 329 types => { 330 $sysname => NSNMP::OCTET_STRING, 331 $counter_oid => NSNMP::INTEGER, 332 }, 333 values => { 334 $sysname0 => 'bob', 335 "$counter_oid.0" => (pack "C", 21), 336 }, 337 ); 338 my $response = NSNMP->decode($agent->handle_request( 339 setreq($sysname0, NSNMP::OCTET_STRING, 'mary'))); 340 ok($response->error_status, 0); 341 ok($response->error_index, 0); 342 $response = NSNMP->decode($agent->handle_request(getreq($sysname0))); 343 ok(($response->varbindlist)[0]->[2], 'mary'); 344 345 $response = NSNMP->decode($agent->handle_request( 346 setreq($sysname0, NSNMP::INTEGER, (pack "C", 31)))); 347 ok($response->error_status, NSNMP::badValue); 348 ok($response->error_index, 1); 349 $response = NSNMP->decode($agent->handle_request(getreq($sysname0))); 350 ok(($response->varbindlist)[0]->[2], 'mary'); 351 352 $response = NSNMP->decode($agent->handle_request( 353 setreq("$counter_oid.0", NSNMP::INTEGER, (pack "C", 31)))); 354 ok($response->error_status, 0); 355 ok($response->error_index, 0); 356 $response = NSNMP->decode($agent->handle_request(getreq("$counter_oid.0"))); 357 ok(($response->varbindlist)[0]->[2], (pack "C", 31)); 358} 359 360# TODO: handling of multiple types 361# TODO: handling of multiple varbinds 362# TODO: handling of different OID spellings 363