344 lines
14 KiB
Plaintext
344 lines
14 KiB
Plaintext
|
Improved Error Reporting and Thread-Safe Use of the SNMP Library
|
||
|
|
||
|
There is a need in some environments to support multiple threads
|
||
|
in a single application. The SNMP Library provides the Single Session
|
||
|
functions which support thread-safe operation when certain precautions
|
||
|
are taken. This document describes the operation of the SNMP Library
|
||
|
with a focus on its session management functions. The Traditional API
|
||
|
and the Single API functions are compared and contrasted.
|
||
|
A working understanding of the CMU or UCD SNMP Library
|
||
|
API is recommended to fully appreciate the concepts discussed.
|
||
|
The document ends with a list of restrictions for using the Single API
|
||
|
in a multi-threaded application.
|
||
|
|
||
|
Unfortunately, the SNMPv3 support was added about the same time as
|
||
|
the thread support and since they occurred in parallel the SNMPv3
|
||
|
support was never checked for multi-threading correctness. It is
|
||
|
most likely that it is not thread-safe at this time.
|
||
|
|
||
|
***** IMPORTANT ANNOUNCEMENT *****
|
||
|
To the point, no resource locks are applied within the SNMP Library.
|
||
|
The APDU encoding and some session management functions can be used
|
||
|
in thread-safe manners. The MIB file parsing is not thread-safe.
|
||
|
The Single Session API was made available in November 1998.
|
||
|
Existing applications use the Traditional API, which is not thread-safe.
|
||
|
The thread-safe considerations are discussed throughout this document.
|
||
|
|
||
|
The research and development of the Single Session API that I've completed
|
||
|
was wholly funded by my employer, Internet Security Systems, Inc.
|
||
|
and is distributed freely to the Internet community.
|
||
|
|
||
|
-Mike Slifcak, 23 April 1999
|
||
|
|
||
|
09 July 1999 Removed references to snmp_synch_setup and snmp_synch_reset
|
||
|
|
||
|
|
||
|
Availability
|
||
|
|
||
|
The Single Session API is integrated into the currently available
|
||
|
versions of the CMU SNMP library and the UC-Davis SNMP package.
|
||
|
|
||
|
ftp://ftp.net.cmu.edu/pub/snmp/cmu-snmp-V1.13.tar.gz and later
|
||
|
Read : snmp_sess_api.3, Changes.SingleSession
|
||
|
|
||
|
ftp://ucd-snmp.ucdavis.edu/ucd-snmp-3.6.tar.gz and later
|
||
|
Read : snmp_sess_api.3, README.thread (after version 3.6.1)
|
||
|
|
||
|
Both libraries work equally well in Windows NT and various
|
||
|
UNIX platforms. Please read this document and refer to
|
||
|
the snmp_sess_api section 3 manual page.
|
||
|
|
||
|
Glossary of Terms
|
||
|
|
||
|
APDU Application Protocol Data Unit
|
||
|
API Application Programming Interface
|
||
|
CMU Carnegie-Mellon University, Pittsburgh, PA.
|
||
|
Library The SNMP library; Both CMU and UCD versions are applicable.
|
||
|
Session Concept embodying the management of transacting SNMP APDUS.
|
||
|
SNMP Simple Network Management Protocol
|
||
|
UCD University of California at Davis, CA.
|
||
|
|
||
|
Introduction
|
||
|
|
||
|
The Library extends the UNIX file concept (open, close, read, write) to a Session.
|
||
|
Opening a Session binds a local socket to a well-known port and creates internal
|
||
|
structures to help with controlling the transaction of SNMP APDUs. Closing a
|
||
|
Session releases the memory and system resources used for these purposes.
|
||
|
|
||
|
Since the mid-1980s, many SNMP applications have used the Traditional Session
|
||
|
API to transact SNMP APDUs between the local host and SNMP-enabled devices.
|
||
|
|
||
|
The Traditional Session API does not support multi-threaded applications:
|
||
|
|
||
|
1) There are no resource locks to prevent exposing the Library's
|
||
|
global data resources to corruption in a multi-threaded application;
|
||
|
|
||
|
2) The Traditional API functions that receive SNMP APDUs
|
||
|
do not provide an interface for one of many sessions;
|
||
|
|
||
|
3) Errors discovered by the Library are communicated through global
|
||
|
data structures and are not associated with the session
|
||
|
in which the error occurred.
|
||
|
|
||
|
The Single Session API provides these capabilities:
|
||
|
|
||
|
1) Manage a single SNMP session safely, in multi-threaded or
|
||
|
non-threaded applications, by avoiding access to data structures
|
||
|
that the Traditional Session API may share between Sessions;
|
||
|
|
||
|
2) Associate errors with the session context for threaded
|
||
|
and non-threaded applications.
|
||
|
|
||
|
|
||
|
Contrasting and Comparing Traditional API and Single API
|
||
|
|
||
|
The Traditional API uses the struct snmp_session pointer returned
|
||
|
from snmp_open() to identify one SNMP session. The Single API uses
|
||
|
the opaque pointer returned from snmp_sess_open() to identify one
|
||
|
SNMP session.
|
||
|
|
||
|
Helpful Hint : The Library copies the contents of the
|
||
|
structure which is input to snmp_open() and snmp_sess_open().
|
||
|
Once copied, changing that input structure's data
|
||
|
has no effect on the opened SNMP Session.
|
||
|
|
||
|
The Traditional API uses the snmp_error() function to identify any
|
||
|
library and system errors that occurred during the processing for
|
||
|
one SNMP session. The Single API uses snmp_sess_error() for the
|
||
|
same purpose.
|
||
|
|
||
|
The Traditional API manages the private Sessions list structure;
|
||
|
adding to the list during snmp_open(), removing during snmp_close.
|
||
|
|
||
|
With few exceptions, the Traditional API calls the Single API
|
||
|
for each session that appears on the Sessions list.
|
||
|
|
||
|
The Traditional API reads from all Sessions on the Sessions list;
|
||
|
The Single API does not use the Sessions list.
|
||
|
The Single API can read from only one Session.
|
||
|
|
||
|
Helpful Hint :
|
||
|
This is the basis for thread-safe-ness of the Library.
|
||
|
There are no resource locks applied.
|
||
|
|
||
|
|
||
|
Using the Single API
|
||
|
|
||
|
A multi-threaded application that deploys the SNMP Library should
|
||
|
should complete all MIB file parsing before additional threads
|
||
|
are activated. Drawing from the parsed contents of the MIB does
|
||
|
not incur any data corruption exposure once the internal MIB structures
|
||
|
are initialised.
|
||
|
|
||
|
The application may create threads such that a single thread may manage
|
||
|
a single SNMP session. The thread should call snmp_sess_init()
|
||
|
to prepare a struct snmp_session structure. The thread can adjust
|
||
|
session parameters such as the remote UDP port or the local UDP port,
|
||
|
which must be set prior to invoking snmp_sess_open().
|
||
|
|
||
|
The first call to snmp_sess_init() initialises the SNMP Library,
|
||
|
including the MIB parse trees, before any SNMP sessions are created.
|
||
|
Applications that call snmp_sess_init() do not need to read MIBs
|
||
|
nor setup environment variables to utilize the Library.
|
||
|
|
||
|
After the struct snmp_session is setup, the thread must call
|
||
|
snmp_sess_open() to create an SNMP session. If at any time
|
||
|
the thread must change the Session configuration,
|
||
|
snmp_sess_session() returns the pointer to the internal configuration
|
||
|
structure (a struct snmp_session, copied from snmp_sess_open).
|
||
|
The thread can adjust parameters such as the session timeout
|
||
|
or the community string with this returned struct snmp_session pointer.
|
||
|
Changes to the remote or local port values have no effect on an opened Session.
|
||
|
|
||
|
The thread can build PDUs and bind variables to PDUs, as it performs its duties.
|
||
|
The thread then calls snmp_sess_send() or snmp_sess_async_send() to build and send
|
||
|
an SNMP APDU to the remote device. If a Get-Response-PDU is expected, the thread
|
||
|
should call snmp_sess_synch_response() instead.
|
||
|
|
||
|
When the thread is finished using the session, it must free the resources
|
||
|
that the Library used to manage the session.
|
||
|
Finally, the thread must call snmp_sess_close() to end the Session.
|
||
|
|
||
|
Snmp_sess_init(), snmp_open(), and snmp_sess_open()
|
||
|
must use the same calling parameter for a given Session.
|
||
|
Other methods should use only the returned parameter from
|
||
|
snmp_open() and snmp_sess_open() to access the opened SNMP Session.
|
||
|
|
||
|
|
||
|
Error Processing
|
||
|
|
||
|
Two calls were added : snmp_error() and snmp_sess_error() return the
|
||
|
"errno" and "snmp_errno" values from the per session data, and a string
|
||
|
that describes the errors that they represent. The string must be freed
|
||
|
by the caller.
|
||
|
|
||
|
Use snmp_error() to process failures after Traditional API calls,
|
||
|
or snmp_sess_error() to process failure after Single API calls.
|
||
|
In the case where an SNMP session could not be opened,
|
||
|
call snmp_error() using the struct snmp_session supplied to either snmp_open()
|
||
|
or snmp_sess_open().
|
||
|
|
||
|
|
||
|
The following variables and functions are obsolete and may create problems
|
||
|
in a multi-threaded application :
|
||
|
|
||
|
int snmp_errno
|
||
|
char * snmp_detail
|
||
|
snmp_set_detail()
|
||
|
snmp_api_errstring()
|
||
|
|
||
|
|
||
|
Function Summary
|
||
|
|
||
|
The functions in the following table are functionally equivalent,
|
||
|
with the exception of these behaviors:
|
||
|
- The Traditional API manages many sessions
|
||
|
- The Traditional API passes a struct snmp_session pointer,
|
||
|
and touches the Sessions list
|
||
|
- The Single API manages only one session
|
||
|
- The Single API passes an opaque pointer, and does not use Sessions list
|
||
|
|
||
|
Traditional Single Comment
|
||
|
=========== ============== =======
|
||
|
snmp_sess_init snmp_sess_init Call before either open
|
||
|
snmp_open snmp_sess_open Single not on Sessions list
|
||
|
snmp_sess_session Exposes snmp_session pointer
|
||
|
snmp_send snmp_sess_send Send one APDU
|
||
|
snmp_async_send snmp_sess_async_send Send one APDU with callback
|
||
|
snmp_select_info snmp_sess_select_info Which session(s) have input
|
||
|
snmp_read snmp_sess_read Read APDUs
|
||
|
snmp_timeout snmp_sess_timeout Check for timeout
|
||
|
snmp_close snmp_sess_close Single not on Sessions list
|
||
|
snmp_synch_response snmp_sess_synch_response Send/receive one APDU
|
||
|
snmp_error snmp_sess_error Get library,system errno
|
||
|
|
||
|
|
||
|
Example 1 : Traditional API use.
|
||
|
|
||
|
#include "snmp_api.h"
|
||
|
...
|
||
|
int liberr, syserr;
|
||
|
char *errstr;
|
||
|
struct snmp_session Session, *sptr;
|
||
|
...
|
||
|
snmp_sess_init(&Session);
|
||
|
Session.peername = "foo.bar.net";
|
||
|
sptr = snmp_open(&Session);
|
||
|
if (sptr == NULL) {
|
||
|
/* Error codes found in open calling argument */
|
||
|
snmp_error(&Session, &liberr, &syserr, &errstr);
|
||
|
printf("SNMP create error %s.\n", errstr);
|
||
|
free(errstr);
|
||
|
return 0;
|
||
|
}
|
||
|
/* Pass sptr to snmp_error from here forward */
|
||
|
...
|
||
|
/* Change the community name */
|
||
|
free(sptr->community);
|
||
|
sptr->community = strdup("public");
|
||
|
sptr->community_len = strlen("public");
|
||
|
...
|
||
|
if (0 == snmp_send(sptr, pdu)) {
|
||
|
snmp_error(sptr, &liberr, &syserr, &errstr);
|
||
|
printf("SNMP write error %s.\n", errstr);
|
||
|
free(errstr);
|
||
|
return 0;
|
||
|
}
|
||
|
snmp_close(sptr);
|
||
|
|
||
|
|
||
|
Example 2 : Single API use.
|
||
|
|
||
|
#include "snmp_api.h"
|
||
|
...
|
||
|
int liberr, syserr;
|
||
|
char *errstr;
|
||
|
void *sessp; /* <-- an opaque pointer, not a struct pointer */
|
||
|
struct snmp_session Session, *sptr;
|
||
|
...
|
||
|
snmp_sess_init(&Session);
|
||
|
Session.peername = "foo.bar.net";
|
||
|
sessp = snmp_sess_open(&Session);
|
||
|
if (sessp == NULL) {
|
||
|
/* Error codes found in open calling argument */
|
||
|
snmp_error(&Session, &liberr, &syserr, &errstr);
|
||
|
printf("SNMP create error %s.\n", errstr);
|
||
|
free(errstr);
|
||
|
return 0;
|
||
|
}
|
||
|
sptr = snmp_sess_session(sessp); /* <-- get the snmp_session pointer */
|
||
|
|
||
|
/* Pass sptr to snmp_sess_error from here forward */
|
||
|
...
|
||
|
/* Change the community name */
|
||
|
free(sptr->community);
|
||
|
sptr->community = strdup("public");
|
||
|
sptr->community_len = strlen("public");
|
||
|
...
|
||
|
if (0 == snmp_sess_send(sessp, pdu)) {
|
||
|
snmp_sess_error(sessp, &liberr, &syserr, &errstr);
|
||
|
printf("SNMP write error %s.\n", errstr);
|
||
|
free(errstr);
|
||
|
return 0;
|
||
|
}
|
||
|
snmp_sess_close(sessp);
|
||
|
|
||
|
Example 3. Differences Between Traditional API and Single API Usage
|
||
|
5a6
|
||
|
> void *sessp; /* <-- an opaque pointer, not a struct pointer */
|
||
|
11,13c12,14
|
||
|
< sptr = snmp_open(&Session);
|
||
|
< if (sptr == NULL) {
|
||
|
---
|
||
|
> sessp = snmp_sess_open(&Session);
|
||
|
> if (sessp == NULL) {
|
||
|
19c20,22
|
||
|
< /* Pass sptr to snmp_error from here forward */
|
||
|
---
|
||
|
> sptr = snmp_sess_session(sessp); /* <-- get the snmp_session pointer */
|
||
|
>
|
||
|
> /* Pass sptr to snmp_sess_error from here forward */
|
||
|
26,27c29,30
|
||
|
< if (0 == snmp_send(sptr, pdu)) {
|
||
|
< snmp_error(sptr, &liberr, &syserr, &errstr);
|
||
|
---
|
||
|
> if (0 == snmp_sess_send(sessp, pdu)) {
|
||
|
> snmp_sess_error(sessp, &liberr, &syserr, &errstr);
|
||
|
33c36
|
||
|
< snmp_close(sptr);
|
||
|
---
|
||
|
> snmp_sess_close(sessp);
|
||
|
|
||
|
|
||
|
Restrictions on Multi-threaded Use of the SNMP Library
|
||
|
|
||
|
1. Invoke SOCK_STARTUP or SOCK_CLEANUP from the main thread only.
|
||
|
|
||
|
2. The MIB parsing functions use global shared data and are not
|
||
|
multi-thread safe when the MIB tree is under construction.
|
||
|
Once the tree is built, the data can be safely referenced from
|
||
|
any thread. There is no provision for freeing the MIB tree.
|
||
|
Suggestion: Read the MIB files before an SNMP session is created.
|
||
|
This can be accomplished by invoking snmp_sess_init from the main
|
||
|
thread and discarding the buffer which is initialised.
|
||
|
|
||
|
3. Invoke the SNMPv2p initialisation before an SNMP session is created,
|
||
|
for reasons similar to reading the MIB file.
|
||
|
The SNMPv2p structures should be available to all SNMP sessions.
|
||
|
CAUTION: These structures have not been tested in a multi-threaded
|
||
|
application.
|
||
|
|
||
|
4. Sessions created using the Single API do not interact with other
|
||
|
SNMP sessions. If you choose to use Traditional API calls, call
|
||
|
them from a single thread. The Library cannot reference an SNMP
|
||
|
session using both Traditional and Single API calls.
|
||
|
|
||
|
5. Using the callback mechanism for asynchronous response PDUs
|
||
|
requires additional caution in a multi-threaded application.
|
||
|
This means a callback function probably should probably not use
|
||
|
Single API calls to further process the session.
|
||
|
|
||
|
6. Each call to snmp_sess_open() creates an IDS. Only a call to
|
||
|
snmp_sess_close() releases the resources used by the IDS.
|
||
|
|