1use strict; 2package NSNMP::Agent; 3# Copyright (c) 2003-2004 AirWave Wireless, Inc. 4 5# Redistribution and use in source and binary forms, with or without 6# modification, are permitted provided that the following conditions 7# are met: 8 9# 1. Redistributions of source code must retain the above 10# copyright notice, this list of conditions and the following 11# disclaimer. 12# 2. Redistributions in binary form must reproduce the above 13# copyright notice, this list of conditions and the following 14# disclaimer in the documentation and/or other materials provided 15# with the distribution. 16# 3. The name of the author may not be used to endorse or 17# promote products derived from this software without specific 18# prior written permission. 19 20# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS 21# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 24# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 26# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 28# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 30# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31use NSNMP; 32use POSIX; 33use NSNMP::Mapper; 34 35# on my 500MHz machine, it handled 2800 requests per second when it 36# peremptorily returned an error message 37 38# adding the ability to actually return values slowed the error 39# messages to 2300 per second; returning messages with values in them 40# was roughly as fast 41 42# adding SNMP SET handling cut it down to 2200 messages per second or 43# so. 44 45# Adding community string handling cut it down to 2100 messages per 46# second or so. 47 48# Adding error handling and get-next handling cut it down to 2000 49# get-requests or 1900 get-next requests per second. On a very small 50# dataset, though; it was down around 1300 for a slightly larger one. 51 52sub new { 53 my ($class, %args) = @_; 54 my $self = bless \%args, $class; 55 $self->{typemapper} = NSNMP::Mapper->new(%{$self->{types}}); 56 $self->{_values} = { 57 map { NSNMP->encode_oid($_) => $self->{values}{$_} } keys %{$self->{values}} 58 }; 59 $self->{_oids} = [sort keys %{$self->{_values}}]; # yay BER oids 60 $self->{lastoid_idx} = 0; 61 $self->{community} ||= 'public'; 62 return $self; 63} 64 65sub _noSuchName { 66 my ($dr) = @_; 67 return NSNMP->encode( 68 request_id => $dr->request_id, 69 error_status => NSNMP::noSuchName, 70 error_index => 1, # XXX 71 type => NSNMP::GET_RESPONSE, 72 varbindlist => [$dr->varbindlist], # XXX? 73 ); 74} 75 76sub _badValue { 77 my ($dr) = @_; 78 return NSNMP->encode( 79 request_id => $dr->request_id, 80 error_status => NSNMP::badValue, 81 error_index => 1, # XXX 82 type => NSNMP::GET_RESPONSE, 83 varbindlist => [$dr->varbindlist], # XXX?? 84 ); 85} 86 87sub next_oid_after { 88 my ($self, $oid) = @_; 89 my $oids = $self->{_oids}; 90 my $lastindex = $self->{lastoid_idx}; 91 my $lastoid = $oids->[$lastindex]; 92 return $oids->[++$self->{lastoid_idx}] || NSNMP->encode_oid('.1.3') 93 if $lastoid and $oid eq $lastoid; 94 for my $ii (0..$#$oids) { 95 if ($oids->[$ii] gt $oid) { 96 $self->{lastoid_idx} = $ii; 97 return $oids->[$ii]; 98 } 99 } 100 return NSNMP->encode_oid('.1.3'); # hope nobody tries to attach a value here 101} 102 103sub handle_get_request { 104 my ($self, $dr, $reqtype) = @_; 105 my @rvbl; 106 for my $varbind ($dr->varbindlist) { 107 my ($oid, $type, $value) = @{$varbind}; 108 $oid = $self->next_oid_after($oid) if $reqtype eq NSNMP::GET_NEXT_REQUEST; 109 # XXX damn, I thought I could avoid decoding this OID: 110 my ($otype, $instance) = $self->{typemapper}->map(NSNMP->decode_oid($oid)); 111 my $ovalue = $self->{_values}{$oid}; 112 return _noSuchName($dr) if not defined $otype or not defined $ovalue; 113 push @rvbl, [$oid, $otype, $ovalue]; 114 } 115 return NSNMP->encode( 116 request_id => $dr->request_id, 117 type => NSNMP::GET_RESPONSE, 118 varbindlist => \@rvbl, 119 ); 120} 121 122sub handle_set_request { 123 my ($self, $dr) = @_; 124 for my $varbind ($dr->varbindlist) { 125 my ($oid, $type, $value) = @{$varbind}; 126 my ($otype, $instance) = $self->{typemapper}->map(NSNMP->decode_oid($oid)); 127 my $ovalue = $self->{_values}{$oid}; 128 return _noSuchName($dr) if not defined $otype or not defined $ovalue; 129 return _badValue($dr) if $type ne $otype; 130 $self->{_values}{$oid} = $value; 131 } 132 return NSNMP->encode( 133 request_id => $dr->request_id, 134 type => NSNMP::GET_RESPONSE, 135 varbindlist => [$dr->varbindlist], 136 ); 137} 138 139sub handle_request { 140 my ($self, $request) = @_; 141 my $dr = NSNMP->decode($request); 142 return undef unless $dr and $dr->community eq $self->{community}; 143 my $type = $dr->type; 144 return(($type eq NSNMP::SET_REQUEST) ? handle_set_request($self, $dr) : 145 handle_get_request($self, $dr, $type)); 146} 147 148sub run { 149 my ($self, $socket) = @_; 150 my ($request, $requestor); 151 for (;;) { 152 if ($requestor = recv $socket, $request, 65536, 0) { 153 my $response = $self->handle_request($request, $requestor); 154 send $socket, $response, 0, $requestor if $response; 155 } else { 156 warn "Error on receive: $!"; 157 } 158 } 159} 160 161# for testing purposes only 162# non-testing would require specifying host and returning errors sensibly 163sub spawn { 164 my ($self, $port) = @_; 165 # note we bind socket before forking, which has two advantages: 166 # - packets never get lost because they got sent before the child 167 # binds the port 168 # - errors kill the main process, not the child. 169 my $listensocket = IO::Socket::INET->new( 170 Proto => 'udp', 171 LocalAddr => "127.0.0.1:$port", 172 ReuseAddr => 1, 173 ); 174 die "Can't bind port $port: $!" unless $listensocket; 175 my $pid = fork(); 176 die "Can't fork: $!" if not defined $pid; 177 if (not $pid) { 178 $self->run($listensocket); 179 POSIX::_exit(0); 180 } 181 $listensocket->close(); # in parent 182 return $pid; 183} 184 185# temp_agent --- test utility function for reaping agents later 186{ 187 my $port = 16165; 188 my @pids; 189 sub temp_agent { 190 my ($self) = @_; 191 $port++; 192 my $pid = $self->spawn($port); 193 push @pids, $pid; 194 return "127.0.0.1:$port"; 195 } 196 sub kill_temp_agents { 197 for (@pids) { kill 9, $_; wait() } 198 @pids = (); 199 } 200} 201 2021; 203