Import from release candidate 2.13.
[debian/dhcpd-pools.git] / contrib / snmptest.pl
1 #!/usr/bin/perl
2
3 # <Note from="Sami Kerola">
4 #    This script is anonymous user contribution (due legal
5 #    department did not let the person the get credit because
6 #    they are afraid open source contributions). You know who you
7 #    are, and I am graceful to get this to be part of the
8 #    package.
9 # </Note>
10
11 # 1) modify snmpd.conf and add the following line.  Note that the
12 #    last field must match the path/name of the script:
13
14 #    pass_persist .1.3.6.1.4.1.2021.250.255 /root/snmptest.pl
15
16 #    This assumes you've already configured snmpd, i.e. community
17 #    strings, etc.
18
19 # 2) Install the following script.  Note that if you changed the
20 #    OID string above, then you'll need to change the value for
21 #    $root in this file, too. Be sure that $datafile is set to
22 #    the location of the .CSV output from dhcpd-pools. NOTE: if
23 #    you set $dbg to 1 then output will be generated in /tmp.
24
25 use strict;
26
27 # Version info:
28 my $SNMPver = "snmp1.0";
29 my $DHCPver = "dhcp1.0";
30 my $VERSION = "$SNMPver/$DHCPver";
31 #
32 # set $dbg to a non-zero value to get debug output in /tmp
33 #
34 my $dbg=0;
35
36 #
37 # Here's an example entry for snmpd.conf:
38 #    pass_persist .1.3.6.1.4.1.2021.250.255 /path/to/this/script.pl
39 # so in this case $root should be '.1.3.6.1.4.1.2021.250.255'
40 #
41 # $root.1.x : shared network name for index x
42 # $root.2.x : IP range name for index x
43 # $root.3.x : Range Contained-In for index x
44 # $root.4.x : Shared Net Stats for index x
45 # $root.5.x : IP range stats for index x
46 #
47 # $root.4.1.x : 'max' value for shared network at index=x
48 # $root.4.2.x : 'cur' value for shared network at index=x
49 # $root.4.3.x : 'tou' value for shared network at index=x
50 #
51 # $root.5.1.x : 'max' value for IP range at index=x
52 # $root.5.2.x : 'cur' value for IP range at index=x
53 # $root.5.3.x : 'tou' value for IP range at index=x
54
55 #
56 # $root must match pass_persis entry in snmpd.conf
57 #
58 my $root='.1.3.6.1.4.1.2021.250.255';
59
60 #
61 # $datafile is the location of the .CSV file created by dhcpd-pools.
62 # NOTE: It is assumed that this file is routinely updated, i.e. at least
63 # every 5 minutes, by some other process, i.e. cron.
64 #
65 my $datafile='/var/lib/dhcp/dhcpstats.csv';
66
67 #
68 # $cachetime determines how long we can cache parse data from $datafile. If
69 # more than $cachetime has elapsed we'll re-read $datafile, otherwise we'll
70 # just send back the last-parsed data
71 #
72 # Generally 60 seconds seems pretty reasonable, but any positive value is
73 # permitted.
74 #
75 my $cachetime = 60;
76
77 #
78 # global variables for DHCP
79 #
80 my %SharedNetwork;
81 my %RangeName;
82 my %RangeContained;
83 my @SNNindex;
84 my @IPRNindex;
85
86 #
87 # global variables for SNMP
88 #
89 my @validoidlist;
90
91 ########## BEGIN UNIQUE-CODE FOR YOUR SNMP-QUERY NEEDS
92 #
93 # ParseDataFile does just that, and stores info in %SharedNetwork for later
94 # processing.
95 #
96 # The routine ParseDataFile is calls at invocation to initialize all
97 # datasets and also periodically (based on $cachetime).
98 #
99 my @where = qw( unknown Ranges SharedNets SumData );
100 sub ParseDataFile () {
101    print DBG "ParseDataFile\n" if $dbg;
102    my $where = 0; # location in file unknown
103    # used to generate ifIndex-like values for each Shared network
104    my $shareindex = 1;
105    # used to generate ifIndex-like values for each Shared network
106    my $rangeindex = 1;
107
108    undef %SharedNetwork;
109    undef %RangeName;
110    undef %RangeContained;
111    undef @SNNindex;
112    undef @IPRNindex;
113    @validoidlist = ();
114
115    open(IN, $datafile) || return undef;
116    while (<IN>) {
117       chomp;
118       print DBG "...Read: $_\n" if $dbg;
119       next if /^\s*$/;   # skip blank lines
120       next if /^"shared net name"/;    # skip titles
121       next if /^"name","max","cur"/;   # skip titles
122       if (/^"Ranges/) { $where = 1; next; }
123       if (/^"Shared/) { $where = 2; next; }
124       if (/^"Sum of/) { $where = 3; next; }
125       print DBG "...Found data for @where[$where]: $_\n" if $dbg;
126       #
127       # "Ranges:"
128       #  0                 1       2        3     4     5   6       7     8
129       # "shared net name","1stIP","lastIP","max","cur","%","touch","t+c","t+c %"
130       #
131       # "Shared networks:" and "Sum of all ranges:"
132       #  0      1     2     3   4       5     6
133       # "name","max","cur","%","touch","t+c","t+c %"
134       #
135       my @data = split /,/;
136       my ($nam, $max, $cur, $tou, $fir); 
137       ($nam = @data[0]) =~ s/"//g;                  # shared net name
138
139       ($fir = @data[1]) =~ s/"//g if ($where == 1); # Range ID (1st IP)
140
141       ($max = @data[3]) =~ s/"//g if ($where == 1); # max # of IPs
142       ($max = @data[1]) =~ s/"//g if ($where >  1); # max # of IPs
143
144       ($cur = @data[4]) =~ s/"//g if ($where == 1); # cur # of IPs
145       ($cur = @data[2]) =~ s/"//g if ($where >  1); # cur # of IPs
146
147       ($tou = @data[6]) =~ s/"//g if ($where == 1); # touched IP's
148       ($tou = @data[4]) =~ s/"//g if ($where >  1); # touched IP's
149
150
151       print DBG "...idx sh/rg=$shareindex/$rangeindex : nam=$nam : max=$max : cur=$cur : tou=$tou\n" if $dbg;
152
153       #
154       # Summary data for each IP range
155       #
156       if ($where == 1) { # Store IP range data
157          if (defined($RangeName{$fir})) {
158             print DBG "...WARNING: duplicate name '$fir'\n" if $dbg
159          }
160          $IPRNindex[$rangeindex] = $fir;
161          $RangeName{$fir} = pack("LLLL", $rangeindex, $max, $cur, $tou);
162          $RangeContained{$fir} = $nam;
163          push @validoidlist, "$root.2.$rangeindex";    # IP range name
164          push @validoidlist, "$root.3.$rangeindex";    # Contained In
165          push @validoidlist, "$root.5.1.$rangeindex";  # range stats
166          push @validoidlist, "$root.5.2.$rangeindex";  # range stats
167          push @validoidlist, "$root.5.3.$rangeindex";  # range stats
168          $rangeindex++;
169       }
170
171       #
172       # Summary data for each Shared network
173       #
174       if ($where == 2) { # Store Shared network summary data
175          if (defined($SharedNetwork{$nam})) {
176             print DBG "...WARNING: duplicate name '$nam'\n" if $dbg
177          }
178          $SNNindex[$shareindex] = $nam;
179          $SharedNetwork{$nam} = pack("LLLL", $shareindex, $max, $cur, $tou);
180          push @validoidlist, "$root.1.$shareindex";    # shared name
181          push @validoidlist, "$root.4.1.$shareindex";  # shared stats
182          push @validoidlist, "$root.4.2.$shareindex";  # shared stats
183          push @validoidlist, "$root.4.3.$shareindex";  # shared stats
184          $shareindex++;
185       }
186
187       #
188       # Summary data for everything
189       #
190       if ($where == 3) { # Store "All" network summary data
191          print DBG "...We don't store 'All' info yet!\n" if $dbg;
192       }
193    }
194    close IN;
195    if ($dbg) {
196       foreach (sort @validoidlist) { print DBG "ValidOID: $_\n"; }
197    }
198
199    if ($dbg) {
200       foreach (@IPRNindex) {
201          print DBG "IP range   $_\n";
202       }
203       foreach (@SNNindex) {
204          print DBG "Shared Net $_\n";
205       }
206    }
207 }
208
209 #############################################################################
210 #############################################################################
211 #############################################################################
212 # Forward Declarations
213 #
214 sub SendReturnNone ();
215 sub SendReturn($$$);
216                  
217 #
218 # GetData gets data, but will leverage caching of data if last parse was
219 # over 60 seconds ago
220 #
221 my $lasttime = 0;
222 sub GetData ($) {
223    my $oid = shift;
224
225    (my $suboid = $oid) =~ s/$root//;
226
227    #
228    # If enough time has elapsed, go fetch fresh data, otherwise use cached
229    # data
230    #
231    print DBG "GetData: Time is: ", time, " : oid $oid->$suboid\n" if $dbg;
232    if ((time - $lasttime) > $cachetime) {
233       ParseDataFile();
234       $lasttime = time;
235    }
236
237    #
238    # Quick sanity check of OID string:
239    #
240    if ( (!($oid =~ /^$root/)) ||
241           ($suboid eq '')   ) { 
242       SendReturnNone();
243       return;
244    }
245
246    #
247    # split the remaining OID pieces to analyse
248    #
249    my @oidlist = split(/\./, $suboid);
250
251    ###################### EDIT THIS FOR YOUR APPLICATION ######################
252    #
253    # If we only get a single value, then barf (we need 2)
254    #
255    my $good = 0;
256    $good = 1 if (($oidlist[1] eq '1') && ($#oidlist == 2)); # shared name
257    $good = 1 if (($oidlist[1] eq '2') && ($#oidlist == 2)); # ip range name
258    $good = 1 if (($oidlist[1] eq '3') && ($#oidlist == 2)); # contained in
259    $good = 1 if (($oidlist[1] eq '4') && ($#oidlist == 3)); # shared-range stats
260    $good = 1 if (($oidlist[1] eq '5') && ($#oidlist == 3)); # ip-range stats
261
262    if (!$good) {
263       print DBG "oidlistcount = $#oidlist and oidlist1 is ", $oidlist[1], "\n" if $dbg;
264       SendReturnNone();
265       return;
266    }
267
268    # $oidlist[1] = 1-5
269    # $oidlist[2] = index for shared-name or range-name
270    #            or datatype to return
271    # $oidlist[3] = index for datatype
272    #
273    if ($oidlist[1] eq '1') {   # looking for name associated with shared net
274       SendReturn($oid, 'string', $SNNindex[$oidlist[2]]);
275       return;
276    }
277
278    if ($oidlist[1] eq '2') {   # looking for name associated with ip range
279       print DBG "Responding with IPRNindex[$oidlist[2]]\n" if $dbg;
280       SendReturn($oid, 'string', $IPRNindex[$oidlist[2]]);
281       return;
282    }
283
284    if ($oidlist[1] eq '3') {   # looking for contained-in info
285       if (defined($IPRNindex[$oidlist[2]])) { # valid subnet!
286          my $fir = $IPRNindex[$oidlist[2]];
287          my $con = $RangeContained{$fir};
288          my @vals= unpack("LLLL", $SharedNetwork{$con});
289          print DBG "fir=$fir : con=$con : vals=@vals : vals0=$vals[0]\n" if $dbg;
290          SendReturn($oid, 'integer', @vals[0]);
291          return;
292       } else {
293          SendReturnNone();
294          return;
295       }
296    }
297
298    if ($oidlist[1] eq '4') {
299       if (defined($SNNindex[$oidlist[3]])) { # valid subnet!
300          my $nam = $SNNindex[$oidlist[3]];
301          my @vals= unpack("LLLL", $SharedNetwork{$nam});
302          print DBG "nam=$nam, vals=@vals, oidlist[2]=$oidlist[2]\n" if $dbg;
303          SendReturn($oid, 'integer', @vals[$oidlist[2]]);
304          return;
305       } else {
306          print DBG "invalid subnet SNN $oidlist[3]\n" if $dbg;
307          SendReturnNone();
308          return;
309       }
310    }
311
312    if ($oidlist[1] eq '5') {
313       if (defined($IPRNindex[$oidlist[3]])) { # valid subnet!
314          my $fir = $IPRNindex[$oidlist[3]];
315          my @vals= unpack("LLLL", $RangeName{$fir});
316          print DBG "fir=$fir, vals=@vals, oidlist[2]=", $oidlist[2], "\n" if $dbg;
317          SendReturn($oid, 'integer', @vals[$oidlist[2]]);
318          return;
319       } else {
320          print DBG "invalid subnet IPRN $oidlist[3]\n" if $dbg;
321          SendReturnNone();
322          return;
323       }
324    }
325    ################## END EDIT THIS FOR YOUR APPLICATION ######################
326 }
327
328 #############################################################################
329 #############################################################################
330 #############################################################################
331 #
332 # From here down the routines should NEVER change
333 #
334 #
335 # SNMP-specific routines
336 #
337
338 { ######## limit scope for some variable
339    #
340    # Compare looks at the OID validoid and userquery
341    # and returns +1 if userquery > validoid
342    # and returns -1 if userquery < validoid
343    # and returns  0 if userquery = validoid
344    #
345    my @validoid;
346    my @userquery;
347    sub Compare () {
348       my $i=1;
349       while (($i <= $#validoid) && ($i <= $#userquery)) {
350          # print DBG "Comparing $validoid[$i] vs $userquery[$i]\n" if $dbg;
351          if ($userquery[$i] > $validoid[$i]) { return +1; }
352          if ($userquery[$i] < $validoid[$i]) { return -1; }
353          $i++;
354       }
355       $i--;
356       my $returnval = 0;
357       $returnval = +1 if (($i <  $#userquery) && ($i == $#validoid));
358       $returnval = -1 if (($i == $#userquery) && ($i <  $#validoid));
359       return $returnval;
360    }
361
362    #
363    # FindNext looks through the list of validoid's trying to find the Next
364    # oid after $oid (aka @userquery)
365    #
366    sub FindNext ($) {
367       my $userqueryoid = shift;
368       print DBG "FindNext($userqueryoid)\n" if $dbg;
369       my $next = $validoidlist[0];
370
371       @userquery = split (/\./, $userqueryoid);
372       my $found = 0;
373       foreach (sort @validoidlist) {
374          $next = $_;
375          print DBG "Comparing $userqueryoid vs. $_\n" if $dbg;
376          @validoid = split (/\./, $_);
377          my $x = Compare();
378          if ($x < 0) {
379             $found = 1;
380             last;
381          }
382       }
383       if (!$found) { return undef; }
384
385       print DBG "Returning $next as next valid OID\n" if $dbg;
386       return $next;
387    }
388
389 } ######### end scope limit
390
391 sub SendReturnNone () {
392    print "NONE\n";
393    print DBG "Sent NONE\n" if $dbg;
394 }
395
396 sub SendReturn($$$) {
397    printf "%s\n%s\n%s\n", shift, shift, shift;
398 }
399
400 my $line=0;
401 sub Get ($$) {
402    my $cmd = shift;
403    my $oid = shift;
404    GetData($oid);
405    $line = 0;
406 }
407
408 sub GetNext ($$) {
409    my $cmd = shift;
410    my $oid = shift;
411    $oid = FindNext($oid);
412    if (defined($oid)) {
413       GetData($oid);
414    } else {
415       SendReturnNone();
416    }
417    $line = 0;
418 }
419
420 sub Set ($$$) {
421    my $cmd = shift;
422    my $oid = shift;
423    my $tv  = shift;
424    print "not-writable\n"; # we don't support snmpset
425    print DBG "Sent not-writable\n" if $dbg;
426    $line = 0;
427 }
428
429 sub Pong {
430    print "PONG\n";
431    print DBG "PONG Sent\n" if $dbg;
432    $line = 0;
433 }
434
435 ################################## START ##################################
436 #
437 # Main
438 #
439
440 select((select(STDOUT), $| = 1)[0]);
441
442 if ($dbg) {
443    open(DBG, ">/tmp/snmp.dbg") || die ("Can't open debug!");
444    select((select(DBG), $| = 1)[0]);
445    print DBG "Version $VERSION\n";
446 }
447
448 # Initialize Data
449 ParseDataFile();
450
451 my $cmd;
452 my $oid;
453 my $tv;
454 while (<STDIN>) {
455    $line++;
456    chomp;
457    s/ //g;
458    tr/A-Z/a-z/;
459    printf DBG "%3d '%s'\n", $line, $_ if $dbg;
460    last if ($_ eq '');
461    $cmd = $_ if ($line  == 1);
462    $oid = $_ if ($line  == 2);
463    $tv  = $_ if ($line  == 3);
464    Pong()               if  ($cmd eq 'ping');
465    Get($cmd, $oid)      if (($cmd eq 'get')     && ($line == 2));
466    GetNext($cmd, $oid)  if (($cmd eq 'getnext') && ($line == 2));
467    Set($cmd, $oid, $tv) if (($cmd eq 'set')     && ($line == 3));
468 }
469
470 print DBG "Clean Exit\n" if $dbg;
471 close DBG if $dbg;
472 exit 0;