<sleinen@common-lisp.net>
This manual documents a Common Lisp library for remote system and network management. It uses the Simple Network Management Protocol (SNMP), version 1 (RFC1157) to request status information from remote devices.
Support for version 2 of the Simple Network Management Protocol (SNMPv2) is forthcoming. See the notes on SNMPv2 in the "Projects" section below.
Sources can be found on https://gitlab.common-lisp.net/cl-snmp/cl-snmp.
Because SNMP is based on ASN.1 (Abstract Syntax Notation One), the library contains functions for constructing and accessing ASN.1 objects, as well as encoding and decoding them using BER (the Basic Encoding Rules).
All symbols documented in this chapter are exported from the
ASN.1
package.
A type representing an ASN.1 object identifier (OID).
Returns an object identifier consisting of the sub-identifiers in the list subids.
Returns the sub-identifiers of oid as a list.
Returns true iff oid1 is a prefix of oid2.
Returns true iff list1 is a prefix of list2. The components of list1 and list2 must be integers permitted in object IDs.
Data type representing an ASN.1 tuple marked with a special type. These objects are often used to encode different types of PDUs.
Returns a typed-asn-tuple
of type type containing the
elements.
Encodes item using BER and returns a byte vector of type
(simple-array (unsigned-byte 8) (*))
.
Hook for decoding application-specific BER sequences.
This variable should be bound around calls to ber-decode
and
asn-read
. Its value must be a function taking three arguments: a
buffer from which it can read BER octets, type (with the
asn-application
flag removed) and length. It should return
whatever application-specific object is encoded that way.
Hook for BER-encoding application-specific objects.
This variable should be bound around calls to ber-encode
and
asn-write
. Its value must be a function taking two arguments:
a buffer to which it can write BER octets, and the object to
encode.
If your application uses several different types of objects that are
encoded, it makes sense to use a generic function as the value of
*application-specific-ber-encoder*
. You can then write a
separate encoding method for each type.
The following functions and macros are a lower level interface to BER
encoding and decoding. In most cases the simpler front-ends
ber-encode
and ber-decode
should be sufficient.
Write the BER-encoded int to buffer. You can give a
type to override the standard ber-int-tag
.
Write the BER-encoded array to buffer. You can give a
type to override the standard ber-octet-string-tag
.
array must be of type (simple-array (unsigned-byte 8) (*))
.
All symbols documented in this chapter are exported from the
SNMP
package.
All session arguments can either be strings or
snmp-session
s. A string argument is interpreted as a host name
and automatically converted into a UDP SNMP session with the given host,
using the standard UDP SNMP port.
Send SNMP a get request for attributes to session. The attributes should be a list of OIDs. If the request succeeds, the result will be a list of two-element lists that maps the given OIDs to the received values. Example:
* (snmp-get "liasg5" '([sysUpTime.0])) (([system.sysUpTime.0] #<21:28:37.12>))
The attributes argument should be a list of OIDs which define MIB subtrees. The function tries to get these subtrees from the SNMP agent defined by session. This works by sending successive get-next requests until either an OID is reached that doesn't fall under the subtree, or the end of the MIB is reached. When multiple OIDs were given, the process terminates as soon as the first subtree is exhausted. Therefore giving multiple OIDs really only makes sense when those define different columns of the same table.
Examples:
* (snmp-get-tree "liasg5" '([system])) ((([system.sysDescr.0] "Silicon Graphics IRIS Indigo2 Extreme running IRIX 5.3") ([system.sysObjectID.0] [.iso.org.dod.internet.private.enterprises.SiliconGraphics.1.120]) ([system.sysUpTime.0] #<21:28:19.18>) ([system.sysContact.0] "Simon Leinen <simon@lig.di.epfl.ch>") ([system.sysName.0] "liasg5.epfl.ch") ([system.sysLocation.0] "INJ 118") ([system.sysServices.0] 72))) * (snmp-get-tree "liasg5" '([ifDescr] [ifMtu])) ((([interfaces.ifTable.ifEntry.ifDescr.1] "Silicon Graphics ec Ethernet controller") ([interfaces.ifTable.ifEntry.ifDescr.2] "Silicon Graphics lo Loopback interface")) (([interfaces.ifTable.ifEntry.ifMtu.1] 1500) ([interfaces.ifTable.ifEntry.ifMtu.2] 8304)))
Decodes an SNMP PDU into an ASN.1 object.
Encodes an SNMP object into a BER-encoded PDU.
The current MIB (Management Information Base), in an internal format. This is mainly used to read and print object IDs in symbolic format.
The agent (for Lisp Machines) also uses this data structure to map incoming OIDs to the functions that manipulate the associated data.
This is a variant of the standard Common Lisp readtable that has one
additional read macro defined for the character #\[
. The read
macro reads an object identifier terminated by #\]
and returns
the corresponding object-id
. If *mib*
is bound to a
Management Information Base, the read macro will recognize the symbols
in this MIB.
SNMP(43): [sysContact.0] #<OBJECT-ID system.sysContact.0>
The package includes a rudimentary SNMP agent for Lisp Machines running the Genera operating system. In this version, it implements a large subset of the MIB-II, but only for reading variables.
Something that might be worth improving is the amount of consage generated when an SNMP PDU is decoded. Currently the whole PDU is decoded into a nested list structure. It would be possible to decode the PDU into several variables representing a fixed expected structure. The decoding code would signal a condition if this structure isn't matched by the PDU.
The neatest interface to such a facility would probably be a macro that
takes a nested variable list, similar to destructuring-bind
, but
with additional information about expected BER type tags. With
such an interface, it would not be necessary to define special-purpose
decoding functions.
It might not be convenient to decode the whole PDU into all
subcomponents at once, because one function may only care about the
outer parts and pass the inner parts to a different function without
wanting to know about it (of course I'm thinking about the
"administrative wrapper" vs. the PDU in SNMP requests). The macro
should provide the possibility to partially parse a PDU and return an
opaque "parsing continuation" as a variable. This can internally be
represented by the ber-input-buffer
structure.
It is not completely clear to me how encoding could be sped up, but maybe this could be done in a similar way.
In addition, the BER encoder could cache previously generated PDUs and reuse them when equally (or similarly?) structured data has to be encoded.
The current compiled-MIB parser should be replaced by something that resembles a real MIB compiler. Ideally, one should be able to take a MIB from an RFC, add Lisp code to it and compile it into an agent.
Better MIB parsing would also make it possible to write a MIB browser, whatever this may be good for.
Here is what has already been done:
snmpv2-session
which is a subclass of
snmp-session
. The former snmp-session
is now called
snmpv1-session
and is another subclass of the new abstract
snmp-session
.
snmpv2-party
) and contexts
(snmpv2-context
).
query->response
that knows how to generate and
decode SNMPv2 PDUs.
And this is what must still be written:
get-bulk
in the agent
get-bulk
in snmp-get-tree
if the session
argument is an SNMPv2 session.
It would also be nice if the sources could be cleaned up after the SNMPv2 implementation, such that SNMPv1 and SNMPv2 share a maximum of code. For example, the requestor and receiver in an SNMPv1 session object are currently represented by their (UDP) transport addresses. They could be represented as a special kind of "SNMPv1" parties.
An interface to sending traps is relatively easy to design--all that is
needed is a function snmp-send-trap
which takes a trap PDU.
The interface for trap receipt is more difficult. My idea is to define
a function (wait-for-snmp-traps callback [timeout]
that implements and endless loop (with optional timeout) waiting for
traps. Whenever a trap PDU is received, it is (decoded and) handed to
the user-supplied callback function. In order for this to be useful,
one would need a multitasking Lisp. But this interface is much easier
to use than one which works under single-tasking. For CMU CL, one could
write a variant of this using CMUCL-style asynchronously woken-up
functions.
set
MIB variables need a set
method to allow remote control of the
LISPMs. At this point, some security should probably be implemented, at
least get/set privileges and MIB views bound to special communities.
This should be done in a way that allows for easy migration to SNMPv2,
i.e. using parties.
MIB-II is now almost completely implemented, at least for reading. However, there are other interesting MIBs that are not. For example RFC 1514, the Host Resources MIB. It includes higher-level information about the operating system, disks, memory, programs and processes than the `system' section of MIB-II. If you want to do real distributed system management as opposed to network management, this will be very interesting.
A LISPM-specific MIB should be defined and implemented. I would think of something like this:
Symbolics = { enterprises ? } lispM = { Symbolics 1 } lispMType lispMUser lispMFep lispMFepVersion lispMMemory lispMMemoryLastFullGC lispMMemoryNextFullGC (settable!) lispMMemoryTotalAllocated lispMMemoryTotalUsed lispMMemoryAreaTable lispMMemoryAreaEntry lispMMemoryAreaIndex lispMMemoryAreaEntryName lispMMemoryAreaRegions lispMMemoryAreaAllocated lispMMemoryAreaUsed ...
Even with library in its somewhat rudimentary current state, it is possible to write management applications that survey the state of manageable remote devices on the network. Using a user interface toolkit such as Garnet, CLUE or CLIM, it would be a nice project to implement the "classical" network visualization tools that draw a visual representation of a network, with nodes changing colors depending on their observed state.
Currently the library only works under recent versions of Allegro, CMU Common Lisp and Genera. It is very straightforward to port to other Lisp dialects that resemble the proposed ANSI standard or CLtL2 and that have some low-level facilities for TCP/IP, in particular UDP.
If the Lisp doesn't have these low-level facilities itself, it is usually possible to implement them using foreign functions from the C library. The Allegro-specific code is an example of how to do this with a socket-based C library. If you are running Unix and have networking, it is highly probable that your C library contains all the functions necessary. Unfortunately the Lisp functions necessary to load these foreign functions are different between Lisp implementations.
As soon as a Lisp-based coffee machine hits the market, I'll personally port the SNMP agent to it, promised!
ASN.1
package
SNMP
package
Back to Common-lisp.net.