Multi(3pm)

πŸ“¦ version 2.1, in ubuntu jammy

πŸ’– SNMP::Multi β€” like, the whole vibe πŸ’–

SNMP::Multi(3pm) β€” User Contributed Perl Documentation β€” SNMP::Multi(3pm)

okay bestie, pull up a chair 🫢 you're about to learn how to do SNMP on a whole bunch of hosts at once and honestly? it's giving main character energy.


✨ NAME ✨

SNMP::Multi β€” Perform SNMP operations on multiple hosts simultaneously. no cap. that's literally the whole job. πŸ’…


πŸ¦‹ SYNOPSIS πŸ¦‹

here's the whole thing, verbatim, because I would never do you dirty like that:

use SNMP::Multi;

my $req = SNMP::Multi::VarReq->new (
    nonrepeaters => 1,
    hosts => [ qw/ router1.my.com router2.my.com / ],
    vars  => [ [ 'sysUpTime' ], [ 'ifInOctets' ], [ 'ifOutOctets' ] ],
);
die "VarReq: $SNMP::Multi::VarReq::error\n" unless $req;

my $sm = SNMP::Multi->new (
    Method      => 'bulkwalk',
    MaxSessions => 32,
    PduPacking  => 16,
    Community   => 'public',
    Version     => '2c',
    Timeout     => 5,
    Retries     => 3,
    UseNumeric  => 1,
    # Any additional options for SNMP::Session::new() ...
)
or die "$SNMP::Multi::error\n";

$sm->request($req) or die $sm->error;
my $resp = $sm->execute() or die "Execute: $SNMP::Multi::error\n";

print "Got response for ", (join ' ', $resp->hostnames()), "\n";
for my $host ($resp->hosts()) {

    print "Results for $host: \n";
    for my $result ($host->results()) {
        if ($result->error()) {
            print "Error with $host: ", $result->error(), "\n";
            next;
        }

        print "Values for $host: ", (join ' ', $result->values());
        for my $varlist ($result->varlists()) {
            print map { "\t" . $_->fmt() . "\n" } @$varlist;
        }
        print "\n";
    }
}

like… look at it. one request object, a whole list of hosts, and it just goes. 🌈


🌸 DESCRIPTION 🌸

so the SNMP::Multi package? it's a mechanism for doing SNMP operations on several hosts at the same time. iconic behavior honestly.

SNMP::Multi builds on G. Marzot's SNMP Perl interface to the UC-Davis SNMP libraries, and it uses asynchronous SNMP operations to send queries/sets to multiple hosts simultaneously. yes, all of them. at once. she doesn't wait around. πŸ’…

Results from all hosts get compiled into a single object, and that object is serving methods to access the data either in aggregate, or broken down by host, or broken down by the individual request. choose your own adventure bestie πŸŽ€

SNMP::Multi supports SNMP GET, SET, GETNEXT, GETBULK and BULKWALK requests. It also performs PDU packing in order to improve network efficiency, when packing is possible. efficiency queen πŸ‘‘


πŸŽ€ OPTIONS πŸŽ€

The SNMP::Multi constructor takes the following options to control its behavior. Any other options are stored and handed to the SNMP::Session constructor when a new SNMP session is created. And because SNMP::Multi's behavior depends on certain SNMP::Session parameters (i.e. Timeout), those get listed below as SNMP::Multi options too. These "overlapped" options get passed un-changed to SNMP::Session's constructor. no edits, no remix, straight through 🫢

  • `Method` β€” Specify one of get, set, getnext, getbulk or bulkwalk. The appropriate SNMP request will be made to each host for each set of variables requested by the user.
⭐ This parameter is required. There is no default value. like, you gotta say something, bestie, she's not a mind reader πŸ’€
  • `Requests` β€” The SNMP::Multi object may be given a new set of requests via the request() method, or by passing a reference to an SNMP::Multi::VarReq object into the constructor. Any VarReq requests given to the SNMP::Multi object through the constructor will be overwritten by subsequent calls to SNMP::Multi::request(). so like, the constructor ones are placeholders at best, fr fr.
This parameter is optional.
  • `PduPacking` β€” The maximum number of variable requests that will be packed into a single SNMP request is controlled by the PduPacking parameter. PDU packing improves the efficiency and accuracy of SNMP requests by reducing the number of packets exchanged. Setting this variable to '0' will disable PDU packing altogether. PDU packing is not performed for SNMP GETBULK or BULKWALK requests. (getbulk/bulkwalk said "we're good actually" πŸ™ˆ)
This optional parameter defaults to the value of $SNMP::Multi::pdupacking.
  • `MaxSessions` β€” This variable controls the maximum number of SNMP sessions that will be kept open simultaneously. Setting MaxSessions higher increases the number of agents being queried at any time, up to the maximum limit of file descriptors available to the process. SNMP::Multi detects "out of resources" conditions (i.e. EMFILE) and adjusts the number of open connections accordingly. she adapts. she's flexible. she's that girl πŸ’…
This optional parameter defaults to the value of $SNMP::Multi::maxsessions.
  • `Concurrent` β€” The value of Concurrent limits the number of requests that may be "in flight" at any time. It defaults to the value of MaxSessions (see above). Setting this value higher may reduce the overall runtime of the SNMP::Multi request, but will also likely increase network traffic and congestion (current maintainer has had SNMP::Multi running smoothly with concurrent set to 512). so 512 is lowkey the vibe. πŸ‘‘
This optional parameter defaults to the value of $SNMP::Multi::maxsessions or the object's 'MaxSessions' parameter.
  • `GetbulkMax` β€” Sets the default "maxrepetitions" value for SNMP GETBULK and BULKWALK requests. This value may be overridden on a per-request basis (by specifying the 'maxrepetitions' parameter in the SNMP::Multi::VarReq constructor). per-request overrides are such a slay 🌟
This optional parameter defaults to the value of $SNMP::Multi::getbulkmax.
  • `ExternalSelect` β€” If ExternalSelect is specified, the SNMP::Multi's execute() method will return immediately after dispatching the first volley of SNMP requests. The caller can then use SNMP::select_info() to get a list of the current file descriptors for the SNMP sessions, and select() on them. When one of the fd's becomes readable, it should be handed to SNMP::reply_cb() to handle it. you're driving now, bestie πŸš—
⚠️ Note that SNMP bulkwalks use the callbacks to dispatch continuing GETBULK requests. This causes the file descriptor to be readable, but SNMP::reply_cb() calls an internal callback in SNMP.xs's bulkwalk implementation, not the SNMP::Multi handler callback. When the walk completes, the SNMP::Multi callback will be called with the specified arguments. iykyk 🧚
  • `Retries` (shared with SNMP::Session) β€” The Retries options specifies the maximum number of retries for each SNMP request. Note that this is the number of retries, not the total number of attempted requests. subtle but so important, don't let it trip you πŸ’€
This optional parameter defaults to the value of $SNMP::Multi::maxretries.
  • `Timeout` (shared with SNMP::Session) β€” The Timeout parameter specifies the timeout in seconds between successive retries for SNMP requests. The overall runtime for the complete SNMP::Multi request will be approximately :
(retries + 1) * timeout
Please note that this is the lower-bound on the time-out. Without sufficient resources (especially file descriptors) to optimize the network communications, completing all requested SNMP operations can take considerably longer. 😭

An over-all timeout may be specified as the optional "timeout" parameter to the SNMP::Multi's execute() method.

This optional parameter defaults to the value of $SNMP::Multi::timeout.
  • `Community` (shared with SNMP::Session) β€” The Community parameter specifies the SNMP community string to use when making requests from SNMP agents. No mechanism exists at this time to specify a different community for individual agents. one community for everybody, that's the rule 🫢
This optional parameter defaults to the value of $SNMP::Multi::community.
  • `Version` (shared with SNMP::Session) β€” The Version option specifies the SNMP protocol to use with the agents. Due to the poor error reporting in SNMP v1, it is recommended that SNMP v2c or v3 be used to communicate with the agents when possible. v1 could never, and I said what I said πŸ’…
This optional parameter defaults to the value of $SNMP::Multi::snmpversion.

✨ METHODS ✨

Okay bestie, the SNMP::Multi object hands you a whole lil toolbox of methods. In most cases you literally only need new(), request(), and execute() β€” the rest is just there for when you're feeling extra. They're documented in roughly the order you'd call them, so it reads like a lil story. πŸŽ€

  • SNMP::Multi::new(...) β€” makes you a shiny new instance of an SNMP::Multi object. The constructor options? Scroll up, they're described above. πŸ‘‘
  • SNMP::Multi::request( <ref to SNMP::Multi::VarReq> ) β€” request() takes the whole set of host/variable requests living inside that SNMP::Multi::VarReq object and hands them over to the SNMP::Multi object. You could also just do this in the constructor with the requests option, but where's the fun in that. πŸ™ˆ
Not me forgetting this: the request() method is NOT cumulative β€” every new call to request() straight up overwrites the previous requests. Previous requests are gone. Poof. πŸ’€
  • SNMP::Multi::execute( [timeout] ) β€” the execute() function does the actual work in SNMP::Multi, and it returns once every request has been answered or has timed out. There's an optional timeout argument you can pass to execute(), which sets an overall timeout β€” doesn't matter how many retries there are or when they happen, this is the big-picture deadline. ⏰

execute() hands back a reference to an SNMP::Multi::Response object, and that object has methods to grab the returned data values all conveniently-like. 🫢

  • SNMP::Multi::error() β€” if something goes sideways while SNMP::Multi is executing, you can call error() to pull out a descriptive string telling you what went wrong. 😭
  • SNMP::Multi::remaining( $req ) β€” this one's iconic. remaining() produces an SNMP::Multi::VarReq stuffed with the requests for any un-answered or un-sent request hunks. You can then pass that VarReq to another SNMP::Multi object (or the same one, we don't judge). This is how an app loops on timeouts, like so:
my $req = SNMP::Multi::VarReq->new( ... );
my $sm  = SNMP::Multi->new( ... );
while ($req) {
    $sm->request($req);
    my $resp = $sm->execute();
    handle_response($resp);

    print "Timeout - retrying" if ($req = $sm->remaining());
}

And here's the glow-up: you can accumulate remaining requests by passing an already existing SNMP::Multi::VarReq object as an argument. The remaining requests get added onto that object. That means you can collect all the remaining ones with ease while looping over a huge number of hosts. Main character efficiency, honestly. πŸ’…


🌸 Building SNMP::Multi::VarReq Requests 🌸

SNMP variable requests get composed and passed to the SNMP::Multi object through a lil auxiliary class called an SNMP::Multi::VarReq. This class is basically a little collector β€” it gathers SNMP requests for variables and hosts (and it can optionally validate them too, if you want). 🧺

The interface to SNMP::Multi::VarReq is super simple, like, only new() and add() methods. They take these arguments:

'vars'           => [ list of Varbinds to be requested (REQUIRED) ]
'hosts'          => [ list of hosts for this variable list ]
'nonrepeaters'   => [ GETBULK/BULKWALK "nonrepeaters" parameter ]
'maxrepetitions' => [ GETBULK/BULKWALK "maxrepetitions" parameter ]

Every single call to new() or add() must contain a list of SNMP variables β€” that part is not optional, bestie. If you don't specify the hosts parameter, the variable list gets requested from all hosts currently known by the SNMP::Multi::VarReq object. And if you do give a host list? Then the variables get requested only from those named hosts. πŸ¦‹

Some simple sanity checks can be run on the VarReq by calling its validate() method, or by setting $SNMP::Multi::VarReq::autovalidate to 1 before you call new(). πŸŽ€

Here's an example of building up a complicated request with new() and add():

Start with:

$r = SNMP::Multi::VarReq->new(
    hosts => [ qw/ A B C / ],
    vars  => [ qw/ 1 2 3 / ]
);

to get:

A: 1 2 3
B: 1 2 3
C: 1 2 3

Now add a var to each host:

$r->add( vars => [ qw/ 4 / ] );

to get:

A: 1 2 3 4
B: 1 2 3 4
C: 1 2 3 4

Add a var to a specific set of hosts:

$r->add( hosts => [ qw/ A C / ],
         vars  => [ qw/ 5   / ] );

to get:

A: 1 2 3 4 5
B: 1 2 3 4
C: 1 2 3 4 5

Finally, create two new hosts and add a pair of vars to them:

$r->add( hosts => [ qw/ D E / ],
         vars =>  [ qw/ 6 7 / ] );

to get:

A: 1 2 3 4 5
B: 1 2 3 4
C: 1 2 3 4 5
D: 6 7
E: 6 7

The SNMP::Multi::VarReq object also gives you a dump() method, which generates a simple dump of the current host/var requests. Perfect for when you're like "what is even in here rn". πŸ‘€


πŸ”₯ SNMP PDU Packing Features πŸ”₯

SNMP::Multi packs SNMP::Varbind requests into bigger request "hunks" so it needs fewer request/response pairs to finish the whole SNMP::Multi request. This packing is controlled by the SNMP::Multi 'PduPacking' parameter. πŸ“¦

For instance β€” say your app creates an SNMP::Multi object with a 'PduPacking' value of 3. SNMP::Multi will pack 5 single SNMP variable requests into two distinct requests. The first request holds the first 3 variables, and the second one gets the remaining two. Math! Slay. ✨

Heads up though: PDU packing is not done for SNMP GETBULK and BULKWALK requests. And you can disable the whole feature by setting the 'PduPacking' parameter to '0'. πŸ›‘

πŸ’œ Accessing SNMP Data From Agent Responses πŸ’œ

The SNMP::Multi::execute() method returns the responses from the SNMP agents inside an SNMP::Multi::Response object. This object is indexed by hostname, and it's made up of per-host response objects (SNMP::Multi::Response::Host's), each one containing a list of SNMP::Multi::Result objects. Those Result objects are what connect an SNMP::VarList with the error status (if there is one) from the SNMP request. And listen β€” an entry only gets made in the Response object if the SNMP agent actually returned some response to SNMP::Multi. No response, no entry. πŸ™…

This is fairly complicated, ngl, but the various objects provide accessor methods so getting at the SNMP responses stays simple. Assume your application is structured something like this example source code:

my $req = SNMP::Multi::VarReq->new( hosts => [...],
                                    vars  => [...] );
my $sm  = SNMP::Multi->new( ... requests => $req, ... );
my $response = $sm->execute( $overall_timeout );
die $sm->error() if $sm->error();

Now you can get at the data through methods on the objects that make up the SNMP::Multi::Response returned by execute(). An SNMP::VarList object gets returned for each variable requested, which normalizes the return format across all SNMP operations (yes, including bulkwalks). 🌈

Peep the SYNOPSIS section above for an example of how to access the SNMP data values after calling the execute() method. ⭐

🌟 SNMP::Multi::Response methods 🌟

  • hostnames() β€” returns a list of the hosts that responded to the SNMP queries made by execute(). πŸ‘‘
  • values() β€” returns all values returned by the SNMP agents, collated into one single list. Use this when your app doesn't care which value came from which specific host (iykyk β€” like summing up octet counts on router interfaces). πŸ“
  • hosts() β€” returns a list of SNMP::Multi::Response::Host objects, one per host queried by the SNMP::Multi::execute() method. πŸŽ€

πŸ¦‹ SNMP::Multi::Response::Host methods πŸ¦‹

  • hostname() β€” returns the hostname associated with this set of responses. You can also just stringify the reference to get the hostname:
print "This is the list of results for $host: \n";
  • values() β€” returns all values received in response to requests made to the associated host. πŸ’–
  • results() β€” returns a list of SNMP::Multi::Result objects for this host. There's one Result object for each request sent to the SNMP agent on this host. ✨

πŸ’… SNMP::Multi::Result methods πŸ’…

The SNMP::Multi::Result object correlates SNMP error information with the response to an SNMP request.

  • error() β€” returns a printable string describing the error encountered for this variable, or undef if no error occurred. 🫢
  • values() β€” returns a list of the values received for this request.
  • varlists() β€” returns an array of SNMP::VarList objects, one per variable requested in the SNMP packet. This format stays consistent across all SNMP operations, and it's required to support bulkwalks (where the number of returned values per variable isn't known a priori by the calling application). No cap. πŸ”₯

🌈 EXAMPLES 🌈

A complete example is given in the "SYNOPSIS" section above. Go look, bestie, it's right there. πŸ’œ


πŸ’€ CAVEATS πŸ’€

The VarList returned for GETBULK requests is "decoded" by SNMP::Multi into an array of single VarLists, one for each requested variable. This behavior differs from the return from the getbulk() method in the SNMP.pm module β€” but it is consistent with the return value of SNMP.pm's bulkwalk() method. Consistency where it counts. ✨

Also, note that the V1 SNMP protocol has very limited error reporting (the agent returns no values, and the 'errind' is set to the index of the offending SNMP variable request). The SNMP::Multi module adjusts the 'errind' index so it indicates which of the variable requests made for a host have failed, regardless of how many actual packets got exchanged. This is necessary to support SNMP::Multi's transparent pdu-packing feature. 🧚

SNMP::Multi relies on features added to the SNMP module by Electric Lightwave, Inc. These features have been incorporated into UCD-SNMP releases 4.2 and later. So you must have SNMP 4.2 or later installed to use this package. Periodt. πŸ‘‘

Using SNMP::Multi with large numbers of hosts or large requests may cause network congestion. 😭 All targets may send PDU's to the originating host simultaneously, which could cause heavy traffic and/or dropped packets at the host. Adjusting the Concurrent and PduPacking variables can mitigate this problem. Tune those knobs, bestie.

Network congestion may be a serious problem for bulkwalks, because multiple packets get exchanged per session. However, network latency and variable target response times cause packets in multiple bulkwalk exchanges to become spread out as the walk progresses. The initial exchange, though? That one will always cause congestion. Every time. πŸ’€

πŸ› BUGS πŸ›

okay so bestie, there's one lil thing missing and we gotta be honest about it:

  • There is no interface to specify a different SNMP community string for a specific host. even though the community is stored on a per-host basis. so like... the data's there, the vibes are there, the API just isn't giving πŸ’…

✨ SEE ALSO ✨

go touch some grass in these other docs, fr fr:


πŸ‘‘ AUTHOR πŸ‘‘

this absolute icon wrote it:

  • Karl ("Terminator rAT") Schilke <rat@eli.net>

yes the nickname is real and yes it eats every single time 🫢


🫢 CONTRIBUTORS 🫢

the besties who helped carry:

  • Joshua Keroes
  • Todd Caine
  • Toni Prug <tony@irational.org>

okay so legal moment, but make it cute:

  • Developed by Karl "Terminator rAT" Schilke for Electric Lightwave, Inc.
  • Copyright (c) 2000-2002 Electric Lightwave, Inc. All rights reserved.
  • Co-maintained by Toni Prug.
This software is provided ``as is'' and without any express or implied warranties, including, without limitation, the implied warranties of merchantibility and/or fitness for a particular purpose.

translation: no promises, bestie, use at your own risk 😭

This program is free software; you may redistribute it and/or modify it under the same terms as Perl itself. slay, open source royalty ✨


πŸ’€ POD ERRORS πŸ’€

not me exposing the doc's messy era but okay, the pod parser said the above document had some coding errors, and here's exactly what it clocked:

  • Around line 93:
    • '=item' outside of any '=over'
  • Around line 250:
    • You forgot a '=back' before '=head1'
  • Around line 257:
    • '=item' outside of any '=over'
  • Around line 268:
    • You can't have =items (as at line 277) unless the first thing after the =over is an =item
  • Around line 326:
    • You forgot a '=back' before '=head1'
    • You forgot a '=back' before '=head1'
  • Around line 444:
    • '=item' outside of any '=over'
  • Around line 480:
    • '=item' outside of any '=over'
  • Around line 551:
    • You forgot a '=back' before '=head1'
it's giving "the pod was fighting for its life" but the content still ate, so we move 🧚

and that's the whole page, bestie β€” this lil manual page is from perl v5.32.0 and it was dated 2021-01-05. go forth and SNMP responsibly πŸ’–βœ¨

πŸ“¦ where this page comes from