Single Session API Introduction This document describes the changes made by Internet Security Systems, Inc. ("ISS") to the SNMP library ("Library") to singly manage SNMP sessions in a multi-threaded application. Familiarity with the Library API is required. To provide a reference for this monologue, let the previously existing SNMP session API be known as the Traditional API, and let the new single SNMP session API be known as the Single API. The Library does not support multi-threaded applications because : 1) The Traditional API functions traverse a shared list of internal data structures, exposing the Library's resources to corruption in a multi-threaded application. 2) The Traditional API functions that manage the message reception logic do not provide an interface for one of many sessions. 3) Errors discovered by the Library are not associated with the session in which the error occurred. The motivation for the described changes include : 1) Manage a single SNMP session safely, in multi-threaded or non-threaded applications; supporting multiple platforms; and do this so that the Library source code is manageable. 2) Associate errors with the session context for threaded and non-threaded applications. The presentation will describe in general terms what the Traditional API does, and how the Single API accomplishes similar actions, but for a single SNMP session. Additional functions are described that make managing easier for applications that use the Traditional and/or Single API. The document ends with a list of restrictions for using the Single API in a multi-threaded application. Disclaimer ISS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL ISS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. Managing SNMP Sessions The Traditional API consists of these functions : struct snmp_session * snmp_open (struct snmp_session *) snmp_send (struct snmp_session *,...) snmp_async_send (struct snmp_session *,...) snmp_synch_response (struct snmp_session *,...) snmp_select_info (struct snmp_session *,...) snmp_read (fd_set *) snmp_timeout () snmp_close (struct snmp_session *) The Single API consists of these functions : void * snmp_sess_open (struct snmp_session *) snmp_sess_send (void *,...) snmp_sess_async_send (void *,...) snmp_sess_synch_response (void *,...) snmp_sess_select_info (void *,...) snmp_sess_read (void *) snmp_sess_timeout (void *) snmp_sess_close (void *) struct snmp_session * snmp_sess_session (void *) Implementation Details The Library manages SNMP sessions through the use of its internal data structures ("IDS") and configurable session data ("CFG"). The IDS points to the CFG and other session control information, including SNMP requests that may be expecting response messages. When a session is opened, the application supplies a CFG. An IDS and a new CFG are created. The application may modify the new CFG to re-configure an opened session. The Traditional API functions use the pointer to new CFG to find the IDS. In doing so, these functions must traverse a singly-linked list, the Sessions list, to match the CFG with each IDS pointer to CFG. The IDS is inserted into the Sessions list when snmp_open() is called. The IDS is removed from the Sessions list when snmp_close() is called. Snmp_select_info(), snmp_read(), snmp_timeout(), and by their inclusion, snmp_synch_response(), search the Sessions list on behalf of all SNMP sessions that expect one or more reply messages. If multiple threads are waiting on the same set of input sources, the shared resources [the Sessions list, the per session request lists] must be mutex locked to prevent corruption when input is received. Resource locking is expensive in a non-Windows NT environment, and is not addressed presently by the Library changes. The Single API functions address the IDS directly using a pointer to void. Since the Single API does not use the Sessions list to find an IDS, there is no resource that needs locking when managing multiple sessions from multiple threads. In most cases, the Traditional API functions trivially reduce to "for" loops that traverse the Sessions list and call Single API functions. It is no accident that the opaque pointer addresses the same structure as a member of the Sessions list. However, this fact cannot be exploited through the Traditional nor Single APIs; the IDS is not defined by the header files. Initial Session Configuration The snmp_sess_init() function was introduced to simplify the setting of default configuration information. The snmp_synch_reset() function was introduced to be the complement to snmp_synch_setup(). All three functions require using a CFG that is input to snmp_sess_open() or snmp_open(), depending on the API used. Once a thread is initialized, it should call snmp_sess_init() to initialize the session data. It can then adjust session parameters such as the remote UDP port or the local UDP port, which must be set prior to creating the SNMP session. If the thread will send SNMP messages that require responses to be synchronously received, the thread must call snmp_synch_setup() before opening the SNMP session. Using the Single Session API A single thread must call snmp_sess_open() to create an SNMP session. Snmp_sess_session() returns the CFG pointer should the application need to adjust parameters such as the session timeout or the community string. The application can then call snmp_sess_send() or snmp_sess_async_send() to build and send an SNMP message to the remote device. If a response is expected, the application should call snmp_sess_synch_response() instead. When the thread is finished using the session, it should call snmp_synch_reset() only if it had called snmp_synch_setup(). Then, the thread should call snmp_sess_close() when the services of the SNMP session are no longer needed. 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 CFG pointer, and finds the IDS on 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 Session Single Session Comment =========== ============== ======= snmp_sess_init snmp_sess_init Both APIs can use snmp_synch_setup snmp_synch_setup Call before the open snmp_open snmp_sess_open Not on Sessions list snmp_sess_session Exposes session pointer snmp_send snmp_sess_send Opaque pointer snmp_async_send snmp_sess_async_send Opaque pointer snmp_select_info snmp_sess_select_info Adds opaque pointer snmp_read snmp_sess_read Adds opaque pointer snmp_timeout snmp_sess_timeout Adds opaque pointer snmp_close snmp_sess_close Not on Sessions list snmp_synch_setup snmp_synch_setup Both APIs can use snmp_synch_reset snmp_synch_reset Both APIs can use snmp_synch_response snmp_sess_synch_response Opaque pointer snmp_synch_reset snmp_synch_reset Call before the close snmp_error snmp_sess_error Opaque pointer Additional Functions Several convenience functions were added. These can be used in sessions managed using the Traditional API and/or the Single API. Snmp_sess_init() prepares the input session data. It supplies the common initialization that existing applications perform. The first call to snmp_sess_init() initializes the 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. Snmp_sess_init() accepts the calling argument to snmp_open() or snmp_sess_open(). Snmp_synch_reset() is the complement to snmp_synch_setup(). It should be called before snmp_close() or snmp_sess_close(), and only if snmp_synch_setup() was called for this session. Both snmp_synch_setup() and snmp_synch_reset() should use the address of the structure which is the input parameter to snmp_open() or snmp_sess_open(). Error Processing The use of these variables and functions are obsolete and supported only for backward compatibility : int snmp_errno char * snmp_detail snmp_set_detail() snmp_api_errstring() 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 special case where an SNMP session could not be opened, call snmp_error() using the calling argument to either snmp_open() or snmp_sess_open(). 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"; snmp_synch_setup(&Session); 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_synch_reset(&Session); 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"; snmp_synch_setup(&Session); 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_synch_reset(&Session); 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. Read the MIB files before an SNMP session is created. 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. CAUTION: Reading MIB files has not been tested in a multi-threaded Windows NT application. 3. Invoke the SNMPv2 initialization before an SNMP session is created, for reasons similar to reading the MIB file. The SNMPv2 structures should be available to all SNMP sessions. CAUTION: These structures have not been tested in a multi-threaded Windows NT 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. Note that the single session API uses the existing callback mechanisms in the SNMP library. This means a callback function cannot use the Single API calls to further process the session. 6. Whether it uses the Traditional API or the Single API, the snmp_synch_setup() function should be called BEFORE snmp_open() or snmp_sess_open().