A System Management Substrate for Common Lisp

Simon Leinen <>


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.

The latest source distribution can be found on our WWW server.

Support for version 2 of the Simple Network Management Protocol (SNMPv2) is forthcoming. See the notes on SNMPv2 in the "Projects" section below.

ASN.1 and BER

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.

Type: object-id

A type representing an ASN.1 object identifier (OID).

Function: make-object-id subids

Returns an object identifier consisting of the sub-identifiers in the list subids.

Function: object-id-subids oid

Returns the sub-identifiers of oid as a list.

Function: oid-prefix-p oid1 oid2

Returns true iff oid1 is a prefix of oid2.

Function: oid-list-prefixp list1 list2

Returns true iff list1 is a prefix of list2. The components of list1 and list2 must be integers permitted in object IDs.

Type: typed-asn-tuple

Data type representing an ASN.1 tuple marked with a special type. These objects are often used to encode different types of PDUs.

Function: make-typed-asn-tuple type elements

Returns a typed-asn-tuple of type type containing the elements.

Function: typed-asn-tuple-type tuple

Function: typed-asn-tuple-elements tuple

BER Encoding and Decoding

Function: ber-decode pdu [:start start] [:end end]

Function: ber-encode item

Encodes item using BER and returns a byte vector of type (simple-array (unsigned-byte 8) (*)).

Variable: *application-specific-ber-decoder*

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.

Variable: *application-specific-ber-encoder*

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.

Macro: with-input-from-ber-buffer (ber-buffer pdu [:start start] [:end end]) forms...

Function: ber-read buffer

Macro: with-output-to-ber-buffer (ber-buffer) forms...

Function: ber-write buffer item

Variable: ber-context

Variable: ber-constructor

Variable: ber-application

Function: ber-read-int-1 buffer type length

Function: ber-read-octet-string-1 buffer type length

Function: ber-write-int buffer int [type]

Write the BER-encoded int to buffer. You can give a type to override the standard ber-int-tag.

Function: ber-write-octet-string buffer array [type]

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-sessions. 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.

Function: snmp-get session attributes

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>))

Function: snmp-get-tree session attributes

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.


* (snmp-get-tree "liasg5" '([system]))

   "Silicon Graphics IRIS Indigo2 Extreme running IRIX 5.3")
  ([system.sysUpTime.0] #<21:28:19.18>)
  ([system.sysContact.0] "Simon Leinen <>")
  ([system.sysName.0] "") ([system.sysLocation.0] "INJ 118")
  ([system.sysServices.0] 72)))
* (snmp-get-tree "liasg5" '([ifDescr] [ifMtu]))

   "Silicon Graphics ec Ethernet controller")
   "Silicon Graphics lo Loopback interface"))
 (([interfaces.ifTable.ifEntry.ifMtu.1] 1500)
  ([interfaces.ifTable.ifEntry.ifMtu.2] 8304)))

Function: snmp-get-next session attributes

Function: snmp-set session attributes

Function: snmp-request session request-code bindings

Function: make-snmp-query session request-code bindings

Function: decode-pdu pdu [:start start] [:end end]

Decodes an SNMP PDU into an ASN.1 object.

Function: encode-pdu pdu

Encodes an SNMP object into a BER-encoded PDU.

Variable: *mib*

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.

Function: print-oid-with-mib

Variable: *snmp-readtable*

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 SNMP Agent

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.

Suggestions for Future Enhancements


More efficient BER decoding (and encoding?)

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.

More powerful MIB parsing

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.

SNMP Protocol

Compatibility with SNMPv2

Here is what has already been done:

  1. Created a new class 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.
  2. Created data structures for parties (snmpv2-party) and contexts (snmpv2-context).
  3. Wrote a variant of query->response that knows how to generate and decode SNMPv2 PDUs.
  4. Implemented distinction between SNMPv1 and SNMPv2 packets in the Lisp Machine agent. The code for replying to SNMPv2 queries is still missing.

And this is what must still be written:

  1. Implementation of authentication and privacy protocols. Consult RFC 1321 for a specification of MD5, which is important for authentication. The standard privacy protocol is based on DES and should be implemented outside the United States to avoid problems with ITAR export restrictions.
  2. Teach the agent to respond to SNMPv2 queries with SNMPv2 responses.
  3. get-bulk in the agent
  4. Use get-bulk in snmp-get-tree if the session argument is an SNMPv2 session.
  5. Define data structures for MIB view and access control objects.
  6. Write a parser for CMU SNMPv2's party, context and acl files.

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.

Implement traps

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.

SNMP Agent

Implement 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.

Support for other MIBs in the Lisp Machine agent

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 }
      lispMMemoryNextFullGC (settable!)

Management Applications

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.

Ports to other systems

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!



  • *application-specific-ber-decoder*
  • *application-specific-ber-encoder*
  • *mib*
  • *snmp-readtable*
  • a

  • ASN.1 package
  • b

  • ber-application
  • ber-constructor
  • ber-context
  • ber-decode
  • ber-encode
  • ber-read
  • ber-read-int-1
  • ber-read-octet-string-1
  • ber-write
  • ber-write-int
  • ber-write-octet-string
  • d

  • decode-pdu
  • e

  • encode-pdu
  • m

  • make-object-id
  • make-snmp-query
  • make-typed-asn-tuple
  • o

  • object-id
  • object-id-subids
  • oid-list-prefixp
  • oid-prefix-p
  • p

  • print-oid-with-mib
  • s

  • SNMP package
  • snmp-get
  • snmp-get-next
  • snmp-get-tree
  • snmp-request
  • snmp-set
  • t

  • typed-asn-tuple
  • typed-asn-tuple-elements
  • typed-asn-tuple-type
  • w

  • with-input-from-ber-buffer
  • with-output-to-ber-buffer

  • This document was generated on 1 July 1998 using the texi2html translator version 1.51.