summaryrefslogtreecommitdiff
path: root/FS/FS/part_export/snmp.pm
blob: 81b3c7eb27ec5df44449b0a18905b79b3b734da6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
package FS::part_export::snmp;

=head1 FS::part_export::snmp

This export sends SNMP SETs to a router using the Net::SNMP package.  It requires the following custom fields to be defined on a router.  If any of the required custom fields are not present, then the export will exit quietly.

=head1 Required custom fields

=over 4

=item snmp_address - IP address (or hostname) of the router/agent

=item snmp_comm - R/W SNMP community of the router/agent

=item snmp_version - SNMP version of the router/agent

=back

=head1 Optional custom fields

=over 4

=item snmp_cmd_insert - SNMP SETs to perform on insert.  See L</Formatting>

=item snmp_cmd_replace - SNMP SETs to perform on replace.  See L</Formatting>

=item snmp_cmd_delete - SNMP SETs to perform on delete.  See L</Formatting>

=item snmp_cmd_suspend - SNMP SETs to perform on suspend.  See L</Formatting>

=item snmp_cmd_unsuspend - SNMP SETs to perform on unsuspend.  See L</Formatting>

=back

=head1 Formatting

The values for the snmp_cmd_* fields should be formatted as follows:

<OID>|<Data Type>|<expr>[||<OID>|<Data Type>|<expr>[...]]

=over 4

=item OID - SNMP object ID (ex. 1.3.6.1.4.1.1.20).  If the OID string starts with a '.', then the Private Enterprise OID (1.3.6.1.4.1) is prepended.

=item Data Type - SNMP data types understood by L<Net::SNMP>, as well as HEX_STRING for convenience.  ex. INTEGER, OCTET_STRING, IPADDRESS, ...

=item expr - Expression to be eval'd by freeside.  By default, the expression is double quoted and eval'd with all FS::svc_broadband fields available as scalars (ex. $svcnum, $ip_addr, $speed_up).  However, if the expression contains a non-escaped double quote, the expression is eval'd without being double quoted.  In this case, the expression must be a block of valid perl code that returns the desired value.

You must escape non-delimiter pipes ("|") with a backslash.

=back

=head1 Examples

This is an example for exporting to a Trango Access5830 AP.  Newlines inserted for clarity.

=over 4

=item snmp_cmd_delete - 

1.3.6.1.4.1.5454.1.20.3.5.1|INTEGER|50||
1.3.6.1.4.1.5454.1.20.3.5.8|INTEGER|1|

=item snmp_cmd_insert - 

1.3.6.1.4.1.5454.1.20.3.5.1|INTEGER|50||
1.3.6.1.4.1.5454.1.20.3.5.2|HEX_STRING|join("",$radio_addr =~ /[0-9a-fA-F]{2}/g)||
1.3.6.1.4.1.5454.1.20.3.5.7|INTEGER|1|

=item snmp_cmd_replace - 

1.3.6.1.4.1.5454.1.20.3.5.1|INTEGER|50||
1.3.6.1.4.1.5454.1.20.3.5.8|INTEGER|1||1.3.6.1.4.1.5454.1.20.3.5.1|INTEGER|50||
1.3.6.1.4.1.5454.1.20.3.5.2|HEX_STRING|join("",$new_radio_addr =~ /[0-9a-fA-F]{2}/g)||
1.3.6.1.4.1.5454.1.20.3.5.7|INTEGER|1|

=back

=cut


use strict;
use vars qw(@ISA %info $me $DEBUG);
use Tie::IxHash;
use FS::Record qw(qsearch qsearchs);
use FS::part_export;
use FS::part_export::router;

@ISA = qw(FS::part_export::router);

tie my %options, 'Tie::IxHash', ();

%info = (
  'svc'     => 'svc_broadband',
  'desc'    => 'Sends SNMP SETs to an SNMP agent.',
  'options' => \%options,
  'notes'   => 'Requires Net::SNMP.  See the documentation for FS::part_export::snmp for required virtual fields and usage information.',
);

$me= '[' .  __PACKAGE__ . ']';
$DEBUG = 1;


sub _field_prefix { 'snmp'; }

sub _req_router_fields {
  map {
    $_[0]->_field_prefix . '_' . $_
  } (qw(address comm version));
}

sub _get_cmd_sub {

  my ($self, $svc_broadband, $router) = (shift, shift, shift);

  return(ref($self) . '::snmp_cmd');

}

sub _prepare_args {

  my ($self, $action, $router) = (shift, shift, shift);
  my ($svc_broadband) = shift;
  my $old;
  my $field_prefix = $self->_field_prefix;

  if ($action eq 'replace') { $old = shift; }

  my $raw_cmd = $router->getfield("${field_prefix}_cmd_${action}");
  unless ($raw_cmd) {
    warn "[debug]$me router custom field '${field_prefix}_cmd_$action' "
      . "is not defined." if $DEBUG;
    return '';
  }

  my $args = [
    '-hostname' => $router->getfield($field_prefix.'_address'),
    '-version' => $router->getfield($field_prefix.'_version'),
    '-community' => $router->getfield($field_prefix.'_comm'),
  ];

  my @varbindlist = ();

  foreach my $snmp_cmd ($raw_cmd =~ m/(.*?[^\\])(?:\|\||$)/g) {

    warn "[debug]$me snmp_cmd is '$snmp_cmd'" if $DEBUG;

    my ($oid, $type, $expr) = $snmp_cmd =~ m/(.*?[^\\])(?:\||$)/g;

    if ($oid =~ /^([\d\.]+)$/) {
      $oid = $1;
      $oid = ($oid =~ /^\./) ? '1.3.6.1.4.1' . $oid : $oid;
    } else {
      return "Invalid SNMP OID '$oid'";
    }

    if ($type =~ /^([A-Z_\d]+)$/) {
      $type = $1;
    } else {
      return "Invalid SNMP ASN.1 type '$type'";
    }

    if ($expr =~ /^(.*)$/) {
      $expr = $1;
    } else {
      return "Invalid expression '$expr'";
    }

    {
      no strict 'vars';
      no strict 'refs';

      if ($action eq 'replace') {
	${"old_$_"} = $old->getfield($_) foreach $old->fields;
	${"new_$_"} = $svc_broadband->getfield($_) foreach $svc_broadband->fields;
	$expr = ($expr =~/[^\\]"/) ? eval($expr) : eval(qq("$expr"));
      } else {
	${$_} = $svc_broadband->getfield($_) foreach $svc_broadband->fields;
	$expr = ($expr =~/[^\\]"/) ? eval($expr) : eval(qq("$expr"));
      }
      return $@ if $@;
    }

    push @varbindlist, ($oid, $type, $expr);

  }

  push @$args, ('-varbindlist', @varbindlist);
  
  return('', $args);

}

sub snmp_cmd {
  eval "use Net::SNMP;";
  die $@ if $@;

  my %args = ();
  my @varbindlist = ();
  while (scalar(@_)) {
    my $key = shift;
    if ($key eq '-varbindlist') {
      push @varbindlist, @_;
      last;
    } else {
      $args{$key} = shift;
    }
  }

  my $i = 0;
  while ($i*3 < scalar(@varbindlist)) {
    my $type_index = ($i*3)+1;
    my $type_name = $varbindlist[$type_index];

    # Implementing HEX_STRING outselves since Net::SNMP doesn't.  Ewwww!
    if ($type_name eq 'HEX_STRING') {
      my $value_index = $type_index + 1;
      $type_name = 'OCTET_STRING';
      $varbindlist[$value_index] = pack('H*', $varbindlist[$value_index]);
    }

    my $type = eval "Net::SNMP::$type_name";
    if ($@ or not defined $type) {
      warn $@ if $DEBUG;
      die "snmp_cmd error: Unable to lookup type '$type_name'";
    }

    $varbindlist[$type_index] = $type;
  } continue {
    $i++;
  }

  my ($snmp, $error) = Net::SNMP->session(%args);
  die "snmp_cmd error: $error" unless($snmp);

  my $res = $snmp->set_request('-varbindlist' => \@varbindlist);
  unless($res) {
    $error = $snmp->error;
    $snmp->close;
    die "snmp_cmd error: " . $error;
  }

  $snmp->close;

  return '';

}


=head1 BUGS

Plenty, I'm sure.

=cut

1;