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