snmp agent

Chapter 8: Embedded SNMP Agent

Overview

When it comes to internetworking management, the Simple Network Management Protocol (or SNMP) is the de facto standard protocol. SNMP has provided remote monitoring and management of internetworking equipment since the late 1980s. SNMP is easily extendable, allowing vendors to add their own management functions under a standard framework.

SNMP provides a very simple architecture for monitoring and management. Because of its simplicity and ubiquity, it's hard to find a piece of networking equipment that doesn't support the SNMP.

In this chapter, we'll discuss the SNMP architecture, protocol and the development of a very simple SNMP agent targeted towards embedded systems. To demonstrate the agent, we'll configure it to provide command and status information for a remotely controlled antenna.

 

SNMP System architecture

SNMP follows a standard client/server architecture (see Figure 8.1) in which the Network Management System (or NMS) is the client to the many SNMP agents distributed throughout the network.


Figure 8.1 SNMP architecture.

One of the problems of network management is the perpetual(不断地) growth of assets (资产、设备)on a network that must be managed. This can become a very difficult problem even if the assets are similar (host computers, routers, embedded devices). Without SNMP, the network administrator would have to manually monitor and administer these devices. With SNMP, as new devices are added to a network the NMS can be notified and then routinely monitor them and the parameters they provide. In this way, errors or failures can be quickly identified to maintain network integrity(完全).

 

 

SNMP Data Structure

Perhaps one of SNMP’s greatest strengths is its simplicity. Objects within SNMP are stored in a tree sorted by their Object IDentifier (对象识别符)(or OID). OIDs are sequences of numbers(数字序列)that identify a particular object within the tree. For example, a standard collection of objects (known in SNMP as a MIB, or Management Information Base) is the System MIB. This MIB contains a number of objects whose values are specific to the device hosting the SNMP agent. One of these objects is known as the System Description(系统描述符) (system.SysDescr.0). As an OID, this object is uniquely identified by the sequence:

     {1.3.6.1.2.1.1.1.0}

The final zero in this OID identifies this variable as a scalar(数量). A scalar is an object that appears once in the tree. This differs from columnar(柱状的) objects that represent multiple instances of an object (such as a table of values). A zero in the final position represents a leaf (or endpoint) in the tree(树中的叶节点).

Textually(原本的), the previous sequence can also be represented as:

     iso(1) org(3) dod(6) internet(1) mgmt(2) mib-2(1) system(1) sysDescr(1)

The MIB tree is quite broad and deep and covers not only network endpoint(端点数据) data but also data for specialized equipment such as Internet routers, gateways, and bridges.


Figure 8.2 Sample portion of the MIB-II tree.

Objects in SNMPv1 are typed. The variables can be non-negative INTEGER (非负整数)values, OCTET_STRINGS or OBJECT_IDENTIFIERS (similar to octet strings). Other specialized types exist such as IPADDRESS for identifying IP addresses and TIMETICKS for representing time.

SNMP Protocol

SNMPv1 provides five basic communication primitives5个基本的通讯原语) (or Protocol Data Units—PDUs). SNMP provides two synchronous(同步的) request messages (请求消息)called GetRequest and GetNextRequest. The GetRequest message takes an object identifier and results in a GetResponse message from the SNMP agent. The GetNextRequest yields(产生) the object after the named object identifier in the request (again via a GetResponse message). SNMP also provides an asynchronous(异步的) response called a trap. A trap is a notification of a condition that is sent directly to the NMS. This is used to notify the NMS of errors that may not be able to wait until the next interrogation (查询周期)cycle. Finally, the SetRequest message allows the NMS to change the values of objects at an SNMP agent. This can be used to command the SNMP agent to perform certain actions (as we'll see later in our embedded scenario(方案)). Figure 8.3 provides a graphical representation of the communication primitives within the architecture.


Figure 8.3 SNMP System Architecture.

All requests follow a very regular structure composed of tuples(元组) called TLVs (or type-length-values)(类型-长度-值). Type is a single byte(单字节) that represents the type of the accompanying value. Length is the length of the value section (in octets) and value is the actual payload data(有效载荷). An SNMP PDU is made up of a sequence of TLVs in a very regular structure as is depicted(描述) in Figure 8.4. To simplify the generation of responses, the target agent simply parses and fills in the requested data as necessary. This is facilitated(使便利) by NULL TLVs that are placed at the locations where the target agent is to insert data.


Figure 8.4 SNMP PDU Format.

Note also in Figure 8.4 that there can be multiple variable bindings. A variable binding is a relationship between an object (identified by the OID) and its value. They are encapsulated(包装) by a sequence TLVTLV序列) that identifies(识别) the length of the variable binding. Multiple variable bindings may appear in a request, and thus in a response. The request contains variable bindings in which the value of the variable is set as NULL. As discussed before, this is a placeholder that the SNMP agent will use to fill in the actual data for the response. The NULL also fulfills the TLV concept by providing the final element of the TLV tuple (although the value is meaningless to the SNMP agent on GetRequest).

Now that we’ve seen conceptually(概念) how an SNMP PDU will appear, let’s dig into the details of an example of both a request and response. In Figure 8.5 is an SNMP GetRequest PDU for the system.sysContact.0 object. The first TLV in the request is called sequence and is used to identify the length of the following TLVs. The sequence type is 0x30 and the next octet is used to decode(解码) the length of this TLV. If the length octet has the high-order bit set (0x80), then the length octet masked with 0x7f yields the number of bytes that follow that will make up the value length (in big-endian order). If the high-order bit is not set, then this octet is the length (no length octets follow).

Since the Length aspect of the TLV can be confusing, let’s look at a couple of examples to fully understand it. Let’s say our sequence TLV is 0x30:0x82:0x00:0x10. Since the length octet (second byte) has the high-order bit set, this octet masked with 0x7f represents the number of octets that follow to specify the actual length. Since 0x82 masked with 0x7f yields 2, the two octets that follow represent the length. We take the next two octets together, which yields 0x0010. Therefore, the 16 octets that follow represent the value portion(值的部分) of this sequence TLV.

Now let’s take the alternate (交替的例子) example. Let’s say our sequence TLV was {0x30 0x01 0x10}. In this case, the length octet does not have the high-order bit set so this octet is our length (or L value), or 0x01. The 1 byte that follows our length is 0x10, so the data encapsulated by this sequence TLV is 0x10 (or decimal 16 octets(十进制的16).


Figure 8.5 SNMP GetRequest PDU for the system.sysContact.0 object.

Back to Figure 8.5, we see the first TLV is a sequence TLV. As discussed before, the sequence TLV includes as its value the length of the TLVs that it encapsulates(封装的). In this case, 0x2f (or decimal 47) octets follow this sequence TLV.

The version TLV specifies the version of the SNMP request. In this case, the value is Integer 0 which represents SNMPv1. The Community type specifies the community string which when parsed is an Octet String TLV of value “public.”. Next is the actual request, which in this case is a GetRequest (type 0xA0). The value of this TLV represents the length of the request. A request is always followed by a Request-ID TLV to correlate(有相关性) responses to their requests at the manager, and an error status and error index TLV. The error status is filled in on response to inform(通知) if any errors occurred. The error index represents the variable binding that the target agent identified as being faulty(有错误的). Next is a sequence type that encapsulates all variable bindings included in the PDU. Again, the value of this sequence TLV is the length of the remaining octets of the variable binding.

Every variable binding is made up of three TLVs, a sequence type for binding, and OID TLV (to identify the object which we’re requesting) and a value TLV. As discussed before, the value in this case is a NULL TLV to be used as a marker for the SNMP agent to insert the actual value.

The SNMP response PDU (GetResponse) is modeled very closely to the request (see Figure 8.6). This is because the request is created in a way to minimize the processing required to generate a response. This of course is theory(理论) and the practicality of this goal is questionable(有疑问的). The first difference to note in the response is that the lengths have all been updated for any new data added to the response. The essential (关键的不同)difference, however, is that the value TLV that has been filled in for the response. The NULL TLV of the variable binding has been filled in with the OID value (as represented in the SNMP MIB table呈现在SNMP MIB 表中) requested in the binding. The binding value is of type Octet-String with a value of 0x04.


Figure 8.6 SNMP GetResponse PDU for the system.sysContact.0 object.

While the TLV structure of SNMP PDUs is conceptually simple, it does offer some complexities. Certain TLVs may be more complicated, depending upon their length, but overall, the structure is both regular and simple.

Extending the internal MIB

The MIB is easily extendable by simply adding new MIB entries to the snmpData table found in the source file  user.c. An entry(条目) to the table has the following structure (shown in Listing 8.1):

Listing 8.1 SNMP MIB entry structure.

typedef struct {

   unsigned char oidlen;

   unsigned char oid[MAX_OID];

   unsigned char dataType;

   unsigned char dataLen;

   union {

      unsigned char octetstring[MAX_STRING];

      unsigned int intval;

   } u;

   void (*function)(void *, unsigned char *);

} dataEntryType;

 

 

The field oidLen is the length of the oid field (object identifier). This is the sequence of digits (数字)that uniquely identify the object. The dataType field identifies the type of data represented by the OID. This can be one of INTEGER, OCTET_STRING, OBJECT_IDENTIFIER, COUNTER or TIME_TICKS. The dataLen field represents the length of the data represented by the OID. A union is used to represent the varying(不同的) types of data that may exist. In our case,(在我们的案例中) a character string and an integer are all that are needed.

The final element is a pointer to a function. This function is used for user-defined data that(在请求时动态生成的) is dynamically generated at request time. An example of dynamic data is the system uptime(正常运行时间) variable. Let's have a look at its initialization. The following structure initialization is based upon the type dataEntryType (from Listing 8.1):

     {8, {0x2b, 6, 1, 2, 1, 1, 3, 0},

        TIME_TICKS, 0, {""},

        currentUptime}

The first field (8) is the length in bytes of the object identifier. This is followed by the actual OID and is represented as a byte array. The next element is the type field of the variable. In this case, the type is TIME_TICKS that is used to represent time data with a least significant(有意义的) bit representing 10ms. The next two fields are the length of the data and the value to be returned upon request. These are both set to null since they will be dynamically filled upon requested. Finally, we specify(指定) our function that will be called when this particular OID is requested.

All dynamic functions return a void pointer(返回一个空指针) to the data and a byte length of the data (as references to the function). See Listing 8.2 for a dynamic function example.

Listing 8.2 Example dynamic function for Uptime.

static void currentUptime(void *ptr, unsigned char *len)

{

  time_t curTime = time(NULL);

  *(unsigned int *)ptr =

       (unsigned int)(curTime - startTime) * 100;

  *len = 4;

}

 

 

The SNMP agent will determine how to treat the data based upon the type stored in the dataEntryType. In the case of TIME_TICKS, the SNMP agent knows to treat this return as an integer.

An Embedded Scenario

To further illustrate(说明) the use of the SNMP agent in an embedded environment, let's create a scenario(方案) and update(更新) the agent MIB to support data collection for it. Consider an embedded system controlling a remote antenna(天线). The antenna can be commanded to point to a desired position (using azimuth(方位角) and elevation(上升、举起)) and provides sync status(同步状态) and signal strength(信号强度) from the onboard receiver.

In the SNMP agent, four variables4个变量) will be used for our interface. These will be grafted(嫁接) to the experimental branch of the MIB tree (shown in Figure 8.7).


Figure 8.7 Antenna(天线) MIB elements.

Not having a spare dish(无线电方面专有名词) and receiver(接收器) lying around, we'll simulate(模仿) this code within the user section of the SNMP agent. Our coordinate  system(坐标系) will use 0 to 180 degrees to represent the range of movement of the antenna(天线). Therefore, if azimuth(方位角) and elevation(提升) are each set to 90, the antenna is in the zenith (顶点)position (that is, pointing straight up).

The first step is to create the MIB entries (产生MIB表项) within the SNMP agent. The accompanying(陪伴、陪同) source includes the system branch of the MIB, so we'll append (附加)this experimental section at the end. In Listing 8.3 is the newly added portion (一部分) of the MIB.

Listing 8.3 New MIB entries for the antenna system.

/* Antenna Azimuth(方位角) Commanded Position */

 {6, {0x2b, 6, 1, 3, 1, 0},

  INTEGER, 4, {""},

  NULL},

 

 /* Antenna Elevation(提升) Commanded Position */

 {6, {0x2b, 6, 1, 3, 2, 0},

  INTEGER, 4, {""},

  NULL},

 

 /* Antenna Receiver Sync Status(天线接收器同步状态) */

 {6, {0x2b, 6, 1, 3, 3, 0},

  OCTET_STRING, 0, {""},

  syncStatus},

 

 /* Antenna Receiver Signal Strength(天线接收器信号强度) */

 {6, {0x2b, 6, 1, 3, 4, 0},

  INTEGER, 0, {""},

  signalStrength}

 

 

Next, we'll add our functions to generate the dynamic data. For our remotely controlled antenna scenario, two variables are dynamically generated(动态生成的). These are shown in Figure 8.4.

Listing 8.4  New antenna system dynamic functions.

static int sigStr = 0;

 

static void syncStatus ( void *ptr, unsigned char *len )

{

  if (snmpData[8].u.intval > 90) {

    *len = sprintf((char *)ptr, "detected");

    sigStr = 99;

  } else {

    *len = sprintf((char *)ptr, "no signal");

    sigStr = 0;

  }

}

 

static void signalStrength ( void *ptr, unsigned char *len )

{

  *(unsigned int *)ptr = sigStr;

  *len = 4;

}

 

 

The snmpData structure represents(代表) our MIB table that contains both the data and the descriptions(描述). One item worth note is the use of snmpData within function syncStatus. To generate simulated data(产生模拟数据), we specify that when the antenna elevation(提升) is greater than 90 degrees, we're pointing to something interesting and a receiver lock(接收器锁定) is achieved  (with associated(相关联的) signal strength). Offset 8 in our MIB table (see Listing 8.4) is the elevation (提高表项) entry and since it's represented(代表) as an INTEGER type, we use the intval element of the union to identify its value.

Now that we've updated our MIB and created our dynamic functions, let's try it out using some of the readily available snmp tools. In this experiment, we'll execute our SNMP agent on top of Linux and then use the UC Davis SNMP tools to work with our custom(定制的) target. As discussed before, developing and testing the protocol on Linux (or any other desktop operating system) is useful to work out any major bugs that may exist before we port to our target environment.

First, let's request (请求) our system MIB from the embedded device. The utility snmpwalk can be used to perform successive snmpget commands to retrieve all objects under the system portion(一部分) of the MIB tree. An example is provided in Listing 8.5.

Listing 8.5 Example usage of snmpwalk utility to communicate with the embedded agent.

     [mtj@plato home]$ ./emsnmp &

     [1] 1147

     Started the SNMP agent

 

     [mtj@plato home]$ snmpwalk plato public system

     system.sysDescr.0 = Embedded Computer

     system.sysObjectID.0 = OID: system.sysObjectID.0

     system.sysUpTime.0 = Timeticks: (3900) 0:00:39.00

     system.sysContact.0 = mtj@mtjones.com

     system.sysName.0 = ec.mtjones.com

     system.sysLocation.0 = B3B44

     system.sysServices.0 = 5

     [mtj@plato mtj]$

 

 

Next, we can interface to our experimental MIB. Since we don't have a MIB compiled with our data, we'll use a numeric object identifier to specify our variables on the target.

     [mtj@plato mtj]$ snmpget plato public .1.3.6.1.3.3.0

     3.0 = "no signal"

     [mtj@plato mtj]$

The object identified by .1.3.6.1.3.3.0 is our receiver sync variable, and our simulation is telling us that the receiver sees no signal. We know from our previous discussion regarding the details of the simulation that all we need to do is command the antenna elevation to a value greater than 90 degrees to see a signal. We accomplish this by setting the elevation variable to a threshold(门限值) value:

     [mtj@plato] snmpset plato public .1.3.6.1.3.2.0 i 91

     2.0 = 91

     [mtj@plato mtj]$

 

Note 

The i 91 represents an integer value of 91. Octet strings may also be set similarly with s <string>.

Finally we read back our sync variable:

     [mtj@plato mtj]$ snmpget plato public .1.3.6.1.3.3.0

     3.0 = "detected"

     [mtj@plato mtj]$

Note that the public argument in the command is the SNMP community name. This is defined in the  emsnmp.h header file and can be changed for security purposes. Since SNMPv1 traffic is performed in clear text (can be sniffed on the network and spoofed) this should not imply(暗示) that SNMP is secure(安全的、牢固的).

Embedded SNMP Implementation

The design of the embedded SNMP agent is quite simple. Thinking back to the GetRequest and GetResponse PDUs, you'll remember that the PDU is made up of a number of constructs (TLVs) that can encapsulate(包装) one another. For example, the initial sequence(开始的序列类型) type includes as its value the remaining octets of the message. The same applies (应用)for the actual(实际的) request, and subsequent(随后的) sequence types. Building a request, or in our case a reply, implies(表明) that we descend (下去) down to the last TLV and build the message in a sense backwards(方向-向后的) (since the lengths of initial TLVs depend upon the lengths of all subsequent(随后的) TLVs).

This problem is reminiscent(使人想起的) of the backpatching(回填) method used in single-pass assemblers(单向传递的编译器). Consider an assembly file that contains code and declarations of variables. If the variables are declared after their use in the source, then the assembler must either make a first pass through the source to identify where they're located, or it must find another solution. Multiple passes through the source are expensive so something else is needed. Backpatching solves this by building a linked list(连接列表) through the locations(位置) that should contain the address (地址) of the undefined variable(未定义的变量) in the generated code(在产生的代码中). The head pointer of the list is inserted into the symbol table at the address location of the undefined entry. When the symbol is finally defined, we "backpatch" through the generated code and replace the list pointers with the actual address of the symbol.

The problem with SNMP message generation is similar. Instead of forward symbol declaration, we have forward (unknown) TLV lengths. The lengths of the TLVs are dependent upon the lengths of the encapsulated TLVs. Therefore, we must know the length of the last TLV(最后一个TLV的长度) in order to calculate the lengths of all preceding TLVs.(前面的TLV的长度)

The solution chosen for this problem is to parse the SNMP request using a predictive(预测) parser and build the response as we go. The usefulness of a predictive parser is that at any point we can look at our current TLV and know exactly how to process it and what comes next (given the regular structure of SNMP PDUs). We predictively parse through the SNMP PDU, and when we reach the final TLV, we return through our function call chain and update (更新)the length values of the TLVs as needed. This method is conceptually(概念的) simple, efficient(高效的), and straightforward to implement(易于实现).

Now that the basic structure of the design has been outlined(纲要), we’ll descend into(降下) the actual code in the next section.

 

 

 

 

 

SNMP Source Code Discussion

Now we'll look at the embedded SNMP agent source code in detail. This code can be found on the CD-ROM at ./software/ch8/emsnmp/.

Datagram Server Setup

The SNMP agent is a datagram server (UDP) that operates on a synchronous (同步的)request/response basis. For each request that is received, an attempt(在方面的尝试) to parse the message is made, and if the SNMPv1 request is valid(正当的), an SNMPv1 response(回应) is generated. Our first step is the creation of a datagram server (see Listing 8.6).

Listing 8.6 SNMP agent datagram server setup.

struct messageStruct {
  unsigned char buffer[1025];
  int  len;//长度
  int  index;//索引
};

  
  
   
    
  
  
static struct messageStruct request, response;

  
  
   
    
  
  
/*---------------------------------------------------------------
 * main() - The embedded SNMP server main
 *--------------------------------------------------------------*/
int main(int argc, char *argv[])
{
  struct sockaddr_in servaddr;//
  struct sockaddr from;//
  int snmpfd, fromlen, retStatus;

  
  
   
    
  
  
  extern void initTable( void ); 
  initTable();//初始化表

  
  
   
    
  
  
  snmpfd = socket(AF_INET, SOCK_DGRAM, 0);//UDP

  
  
   
    
  
  
  bzero(&servaddr, sizeof(servaddr));//置零
//memset
  servaddr.sin_family = AF_INET;
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  servaddr.sin_port = htons(161);

  
  
   
    
  
  
  retStatus = bind(snmpfd, (struct sockaddr *)&servaddr, 
               sizeof(servaddr));//绑定socket

  
  
   
    
  
  
  if (retStatus < 0) {
printf("Unable to bind to socket (%d)./n", errno);
//不能绑定socket
    exit(-1);
  }

  
  
   
    
  
  
  printf("Started the SNMP agent/n");
  for ( ; ; ) {
    fromlen = sizeof(from);//
    request.len = recvfrom(snmpfd, &request.buffer[0], 1024, 0, 
                    &from, &fromlen);

  
  
   
    
  
  
    printf("Received datagram from %s, port %d/n", 
           inet_ntoa(((struct sockaddr_in *)&from)->sin_addr),
           ((struct sockaddr_in *)&from)->sin_port);

  
  
   
    
  
  
    if (request.len >= 0) {

  
  
   
    
  
  
      request.index = response.index = 0;
      errorStatus = errorIndex = 0;

  
  
   
    
  
  
      if (parseSNMPMessage() != -1) {
        sendto(snmpfd, response.buffer, response.index, 0, 
                (struct sockaddr *)&from, fromlen);
      }

  
  
   
    
  
  
    }

  
  
   
    
  
  
  }

  
  
   
    
  
  
  close(snmpfd);
  return(0);
}

 

 

After initializing our SNMP MIB table (via initTable, covered in the next section) we create our server socket(在初始化SNMP MIB表后,创建服务器socket). The socket is of type SOCK_DGRAM to represent a datagram socket. We build our sockaddr structure to accept datagrams from any interface (INADDR_ANY) on this host. The port is restricted to 161 which is the reserved(预留的端口) port for SNMP communication.

Once the socket is set up(启动), we go into a loop to accept requests(接收请求) through the socket. Requests are received through the recvfrom call. We use this call instead of recv because the datagram socket is unconnected and we need the peer(对方的信息) information in order to send back a response.

Next, we do some initialization and call the function parseSNMPMessage. This is our predictive parser for SNMP; it performs all activities for both parsing and generating a valid (恰当的回应)response. Once this function returns and informs us that a valid response message was constructed(构造), we use sendto to transmit(发送) the response(回应) back to the requester(请求者). Note that in the sendto call, we're using the from and fromlen parameters (参数)that we originally(最初) received through the recvfrom call (i.e., the source of the request).

At this point, our dialog is complete for this request and we return to the top of the loop to await (等待)a new request. It should be clear from this example that SNMP is a stateless protocol(无状态协议); the fact that each request message is performed in isolation(隔离) of others never alters(改变)the behavior of subsequent(随后的) requests.

A final item to note in this listing is the messageStruct structure(结构). Both the request and response variables are derived(来源于) from this. The structure simply encapsulates(包装) the buffer, length of data contained within the buffer, and the working index that's used within the parser and response generator(发生器). These are declared global within this function and are used widely within the parsers.

The SNMP MIB Table

SNMP provides access primarily(主要的) to a tree of data. A network manager can request the values in an agent's tree and change those values. One example subtree used in this embedded agent is very simple, shown in Listing 8.1. The System MIB, which is a standard element present on all devices, is initialized(初始化) within  user.c (see Listing 8.7).

Listing 8.7 SNMP agent table initialization.

time_t startTime;

  
  
   
    
  
  
static void currentUptime(void *, unsigned char *);

  
  
   
    
  
  
dataEntryType snmpData[]={

  
  
   
    
  
  
  /* System MIB */

  
  
   
    
  
  
    /* SysDescr Entry */
    {8, {0x2b, 6, 1, 2, 1, 1, 1, 0}, 
       OCTET_STRING, 17, {"Embedded Computer"}, 
       NULL},
  
    /* SysObjectID Entry */
    {8, {0x2b, 6, 1, 2, 1, 1, 2, 0}, 
       OBJECT_IDENTIFIER, 8, {"/x2b/x06/x01/x02/x01/x01/x02/x00"},
       NULL},

  
  
   
    
  
  
    /* SysUptime Entry */
    {8, {0x2b, 6, 1, 2, 1, 1, 3, 0}, 
       TIME_TICKS, 0, {""},
       currentUptime},

  
  
   
    
  
  
    /* sysContact Entry */
    {8, {0x2b, 6, 1, 2, 1, 1, 4, 0}, 
       OCTET_STRING, 15, {"mtj@mtjones.com"}, 
       NULL},

  
  
   
    
  
  
    /* sysName Entry */
    {8, {0x2b, 6, 1, 2, 1, 1, 5, 0}, 
       OCTET_STRING, 14, {"ec.mtjones.com"}, 
       NULL},

  
  
   
    
  
  
    /* Location Entry */
    {8, {0x2b, 6, 1, 2, 1, 1, 6, 0}, 
       OCTET_STRING, 5, {"B3B44"},
       NULL},

  
  
   
    
  
  
    /* SysServices */
    {8, {0x2b, 6, 1, 2, 1, 1, 7, 0}, 
       INTEGER, 4, {""}, 
       NULL},

  
  
   
    
  
  
};

  
  
   
    
  
  
const int maxData = (sizeof(snmpData) / sizeof(dataEntryType));

  
  
   
    
  
  

  
  
   
    
  
  
/* MIB Initialization */

  
  
   
    
  
  
void initTable( void )
{

  
  
   
    
  
  
  startTime = time(NULL);

  
  
   
    
  
  
  /* Note:  The C spec permits the auto-initialization of the 
   * first member of a union.  Therefore, since the first member 
   * of the union is a string, we initialize the 'u.intval' elements
   * here.
   */

  
  
   
    
  
  
  snmpData[6].u.intval = 5; /* System.Services */

  
  
   
    
  
  
}

  
  
   
    
  
  
/* Dynamic Data Functions */

  
  
   
    
  
  
static void currentUptime(void *ptr, unsigned char *len)
{
  time_t curTime = time(NULL);
  *(unsigned int *)ptr = 
     (unsigned int)(curTime - startTime) * 100;
  *len = 4;
}

 

 

Most of the initialization is performed through static(静态的) initialization upon declaration of the snmpData structure array. Each entry in the array is a dataEntryType structure and is block initialized. We keep track of the number of elements in the table by simply dividing the total size of the table by the size of a single member.

The initTable function performs (执行) additional initialization not provided by the table. In initTable we initialize a startTime variable for use in the uptime MIB entry. Finally, we initialize any intval union entries(表项). These entries are initialized here because they cannot be initialized(初始化) in the table during static initialization. This is due to the way C performs auto-initialization of unions(单元的自动初始化). When a union is statically initialized, only the first element of the union may be set. Therefore, since the intval is the second element of the union, it must be initialized separately(单独的初始化) and is therefore performed here.

Finally, in this code example there is a dynamic function. This function will be called when an SNMP request arrives for the OID entry that is provided by this function. If we refer back to the structure initialization in Listing 8.7, the SysUptime entry sets a function pointer(函数指针) for currentUptime. This function (as do all dynamic functions (动态函数) in this architecture(结构)) passes the value for the OID back through an argument by reference. Dynamic functions(动态函数) allow us to know when a request is made and keeps us from having to update the table every time a value changes (for example, an A/D sensor). Whether string or integer values are returned, all are interpreted(解释) as void pointers(空指针) and the SNMP agent figures out which is appropriate from the type within the SNMP table.

Managing the SNMP MIB Table

Two basic functions are provided for retrieving (检索数据) data and storing data (存储数据)in the SNMP MIB table. These are getEntry and setEntry and are shown in Listing 8.8.

The getEntry function takes as its arguments (参数)an id (represents an index into the SNMP table) and then three arguments that are passed by reference to allow their values to be altered(改变). This call is used from within the parser(解析器) for a GetRequest or GetNextRequest PDU. The dataType argument is the actual type of the SNMP entry (OCTET_STRING, INTEGER, etc.). The argument ptr is a void pointer(空指针) used to return the value of the entry and len is its length. All arguments are simply retrieved from the table and returned(从表格检索并返回).

The only complexity(复杂性) of getEntry is that it must determine what type of data is being returned and then pass it back accordingly. For types OCTET_STRING and OBJECT_IDENTIFIER, the data is passed back as a character string. Types INTEGER, TIME_TICKS, COUNTER and GAUGE are integer types. Both branches of the case simply cast the void pointer to the type being returned and then copy the data normally.

The function getEntry also supports dynamic data(动态数据) whereby the actual data being returned is created at request time(在请求时间创建). A row(行) in the table providing dynamic data is identified by its dynamic function variable being non-null(非空) ((包含一个函数指针)contains a function pointer). In these cases, the dynamic function is called and the function fills in the variable and length by reference (索引)(instead of retrieving a value from the table).

The setEntry function provides the means(方法) to set an SNMP entry to a value specified in the request. It is called from within the SNMP parser in response to an SNMP SetRequest PDU. The setEntry function has the same basic structure as getEntry. Depending upon the type of the SNMP entry, the data is manipulated(操作) as either a character string or integer.

Another support function called getValue is found here. This function is used to create an integer value(整数值) from a binary value(二进制值) with a given octet length.

Listing 8.8 SNMP agent table access functions.

static int getValue( unsigned char *vptr, int vlen)
{
  int index = 0;
  int value = 0;

  
  
   
    
  
  
  while (index < vlen) {
    if (index != 0) value <<= 8;
    value |= vptr[index++];
  }

  
  
   
    
  
  
  return value;
}

  
  
   
    
  
  

  
  
   
    
  
  
int getEntry ( int id, unsigned char *dataType, 
                void *ptr, int *len )
{

  
  
   
    
  
  
  if (!((id >= 0) && (id < maxData))) return INVALID_ENTRY_ID;

  
  
   
    
  
  
  *dataType = snmpData[id].dataType;

  
  
   
    
  
  
  switch(*dataType) {
    case OCTET_STRING :
    case OBJECT_IDENTIFIER :
      {
        unsigned char *string = ptr;
        int j;

  
  
   
    
  
  
        if (snmpData[id].function != NULL) {
          snmpData[id].function( 
                          (void *)&snmpData[id].u.octetstring, 
                            &snmpData[id].dataLen );
        }

  
  
   
    
  
  
        *len = snmpData[id].dataLen;
        for (j = 0 ; j < *len ; j++) {
          string[j] = snmpData[id].u.octetstring[j];
        }
      }
      break;
      
    case INTEGER :
    case TIME_TICKS :
    case COUNTER :
    case GAUGE :
      {
        int *value = ( int * )ptr;

  
  
   
    
  
  
        if (snmpData[id].function != NULL) {
          snmpData[id].function( (void *)&snmpData[id].u.intval,
                                  &snmpData[id].dataLen );
        }

  
  
   
    
  
  
        *len = sizeof(unsigned int);
        *value = htonl(snmpData[id].u.intval);

  
  
   
    
  
  
      }
      break;

  
  
   
    
  
  
    default : 
       return INVALID_DATA_TYPE;
       break;

  
  
   
    
  
  
  }

  
  
   
    
  
  
  return SUCCESS;
}

  
  
   
    
  
  

  
  
   
    
  
  
int setEntry ( int id, void *val, int vlen, 
          unsigned char dataType, int index)
{

  
  
   
    
  
  
  int retStatus=OID_NOT_FOUND;
  int j;

  
  
   
    
  
  
  extern int errorStatus, errorIndex;

  
  
   
    
  
  
  if (snmpData[id].dataType != dataType) {
    errorStatus = BAD_VALUE; 
    errorIndex = index;
    return INVALID_DATA_TYPE;
  }

  
  
   
    
  
  
  switch(snmpData[id].dataType) {
    case OCTET_STRING :
    case OBJECT_IDENTIFIER :
      {
        unsigned char *string = val;
        for (j = 0 ; j < vlen ; j++) {
          snmpData[id].u.octetstring[j] = string[j];
        }
        snmpData[id].dataLen = vlen;
      }
      retStatus = SUCCESS;
      break;
      
    case INTEGER :
    case TIME_TICKS :
    case COUNTER :
    case GAUGE :
      {
        snmpData[id].u.intval = 
                      getValue( (unsigned char *)val, vlen);
        snmpData[id].dataLen = vlen;
      }
      retStatus = SUCCESS;
      break;

  
  
   
    
  
  
    default : 
       retStatus = INVALID_DATA_TYPE;
       break;

  
  
   
    
  
  
  }

  
  
   
    
  
  
  return retStatus;
}

 

 

The Parser and Response Generator

The meat(主要的部分) of the SNMP agent is the request PDU parser and response PDU generator(请求PDU解析器和回应PDU解析器). These two elements of the agent are performed together when a response message is generated as the parse progresses through the request message.

The basic structure of the parser is a collection of functions whose job it is to parse a particular kind of TLV(解析一种特殊的TLV. Each of the parser functions is called as a chain(链) and each knows what should follow. From Listing 8.3, we can see that the function parseSNMPMessage is called that's responsible for parsing the sequence TLV of which all SNMP PDUs begin. The version TLV always follows the initial sequence TLV, and therefore the parseSNMPMessage function calls the parseVersion function.

All of the parsing functions follow a very similar pattern(简单的模式). First, the function parseTLV is called which does some very simple parsing of the TLV and initializes a TLV structure to identify TLV在请求消息的位置)where the TLV sits within the request message. Next, the TLV is checked to ensure it's what we expect at this point in the parse. We'll then copy the TLV from request message to the response message and record (将位置记录在一个局部变量respLoc中)the position in a local variable called respLoc. At this point, we've done all of the parsing we can do for this TLV, so the next job is to call the next parsing function in the chain. When the parser (返回)returns, we record (记录) the length that was returned(返回) which represents (代表)the total size of the PDU from our position to the end. Finally, we return the length with our size added to give the caller an accurate(精确的尺寸) size from its perspective(观点).

One important point to notice here is that while a parser function calls another parser function in line, it can do processing before the function and processing afterward. Processing after the next parser is called allows us to perform the basic backpatching(回填) of lengths that we discussed earlier in this chapter. For example, the length returned by the parser is the octet length of all TLVs that follow the current TLV. Therefore, if we need to record a length in the current TLV, that length comes from the return of the called parser function. We in turn pass that length plus our own size up to the previous caller to continue the process. This is the purpose of recording the location of respLoc. This is the index in the response message where we insert our length to generate a valid response(产生一个有效的回应).

Basic parsing primitives(原语)

Before jumping into the SNMP parsers, let's first look at some of the basic primitives for parsing unspecified TLVs (see Listing 8.9). The structure tlvStrucType is used to hold the information about a TLV (which is held locally in scope by each parsing function). The structure contains the start index of the TLV, it's length (the L value of the TLV), the index of the Value (V portion of TLV) and the index of the next TLV. We create a local TLV structure and pass it to the function parseTLV. This function performs(执行) the TLV parsing and fills in(填充) the elements(元素) of the structure. Note that this function must understand the type of the TLV since it affects the calculation of the next TLV index.

The parseLength function is also provided here which performs length calculation (长度计算)(computes the L(长度) portion of the TLV). As discussed earlier in this chapter, a length can be a single byte, or a series of bytes depending upon the high-order bit setting of the L byte.

Listing 8.9 SNMP TLV parsing routines(惯例).

typedef struct {
  int start;     /* Absolute Index of the TLV */
  int len;     /* The L value of the TLV */
  int vstart; 值的开始位置  /* Absolute Index of this TLV's Value */
  int nstart;下一个tlv的开始位置   /* Absolute Index of the next TLV */
} tlvStructType;

  
  
   
    
  
  
static int parseLength(const unsigned char *msg, int *len)
{
  int i=1;

  
  
   
    
  
  
  if (msg[0] & 0x80) {
    int tlen = (msg[0] & 0x7f) - 1;
    *len = msg[i++];

  
  
   
    
  
  
    while (tlen--) {
      *len <<= 8;
      *len |= msg[i++];
    }

  
  
   
    
  
  
  } else {
    *len = msg[0];
  }

  
  
   
    
  
  
  return i;
}

  
  
   
    
  
  

  
  
   
    
  
  
static int parseTLV(const unsigned char *msg, int index, 
               tlvStructType *tlv)
{
  int Llen = 0;

  
  
   
    
  
  
  tlv->start = index;

  
  
   
    
  
  
  Llen = parseLength((const char *)&msg[index+1], &tlv->len );

  
  
   
    
  
  
  tlv->vstart = index + Llen + 1;

  
  
   
    
  
  
  switch (msg[index]) {
    case SEQUENCE:
    case GET_REQUEST:
    case GET_NEXT_REQUEST:
    case SET_REQUEST:
      tlv->nstart = tlv->vstart;
      break;
    default:
      tlv->nstart = tlv->vstart + tlv->len;
      break;
  }

  
  
   
    
  
  
  return 0;
}

 

 

Parsing and Response Generation Functions

Now let's look at the SNMPv1 parser. The first function in the parser is parseSNMPMessage. This function is responsible for parsing the initial sequence-of TLV that begins all SNMP request PDUs. Since the basic designs of all TLV parsers are similar, we'll cover this one in more detail and then discuss the other TLV parsers, noting where differences occur(发生).

parseSNMPMessage

In parseSNMPMessage, we first call the parseTLV  function with our local TLV variable along with the request PDU buffer and current index(当前的索引). The current index is always zero at this point because we're just starting the parse phase(阶段). When this function returns, we'll have an initialized tlv variable that represents(代表) the sequence (顺序)TLV in the request PDU. Our first test of this TLV is just a little sanity checking to make sure that it actually is a SEQUENCE_OF type. Note that the SEQUENCE_OF and SEQUENCE types are the same value (0x30), which doesn't present a problem because we know what to expect at each step of the parse due to the SNMP request's very predictable(可预言的) structure. If the type doesn't match, then we return with a -1 error return. This could be due to a message received in error (that is, we received something other than an SNMPv1 request(请求)).

Listing 8.10 The SNMP parser entry (parseSNMPMessage).

static int parseSNMPMessage ( )
{
  int size = 0, seglen, ret, respLoc;
  tlvStructType tlv;

  
  
   
    
  
  
  ret = parseTLV(request.buffer, request.index, &tlv);
//返回值
  if (request.buffer[tlv.start] != SEQUENCE_OF) return -1;

  
  
   
    
  
  
  seglen = tlv.vstart - tlv.start;
  respLoc = tlv.start;
  COPY_SEGMENT(tlv);

  
  
   
    
  
  
  size = parseVersion();

  
  
   
    
  
  
  if (size == -1) return -1;
  else size += seglen;

  
  
   
    
  
  
  insertRespLen(tlv.start, respLoc, size);
//response中插入长度

  
  
   
    
  
  
  return ret;
}

 

 

Next, we determine the segment length (seglen) of our TLV. We use this value to determine what to copy to the response message (remember, we build our response as we parse and return later to fill in any necessary elements that are not currently known(现在不知道的)). In this case, the segment length is (vstart – start) which is the length of the beginning of the TLV to the v部分)V portion. We use this instead of the entire TLV (nstart - start) because not doing so would copy the entire PDU (全部PDUto the response since this TLV has a value of the remaining TLVs in the request. Therefore, we copy only the "TL" portion and allow(允许) the next parse function to deal with(处理下一个TLV the next TLV.

We save the respLoc here which is a local index of the location(本地的位置索引) to insert the length when we return. Note that after the parseVersion call (to parse the next TLV), we adjust(天正) the size for the segment length and then call insertRespLen to insert the length into the SNMP response PDU for this TLV.

The COPY_SEGMENT macro(宏) is used to copy the request TLV to the response. The macro uses the seglen variable as the length of the TLV to copy only (只拷贝TLV中我们需要的部分)the portion of the TLV that we need.

The next element(部分) to parse (描述、分析)after the initial sequence TLV is the version TLV(版本TLV. It's important to note here that once this function returns, we have parsed the entire SNMP request PDU(全部snmp请求pdu. The return value(返回值) is then the number of octets(八进制) that followed this TLV in the response. This is important because while there will be a one-to-one correspondence to TLVs in the request and response, there may not be a one-to-one correspondence in their lengths(在产度). This is because for GetRequest and GetNextRequest, the NULL TLVs in the request ((两个八进制值)of two octets in size) will be replaced with the actual value TLV in the response (as retrieved(恢复) from the SNMPData table).

Finally, we insert(插入) the length into the response for this TLV (using insertRespLen) and then return to the UDP server(返回给UDP服务器). In all other TLV parsers, we'll return the length thus far except in this case. Since there are no parsers before this function, we'll simply return an error code that will be 0 on success and -1 on error. If the UDP server receives the -1 error code, it will silently(仅仅) ignore the request and not generate a response.

parseVersion

The parseVersion function parses(解析) the version TLV. The version TLV is an integer TLV which when reduced(减少) results in a single number. In our case, the TLV is represented(表示) as {0x02 0x01 0x00} which is an Integer Type (0x02) of Length 1 octet (0x01) with a Value of 0 (0x00).

Once the TLV has been parsed in our tlv structure, we'll do some simple error checking(差错). This agent implementation works only with SNMPv1 PDUs and therefore the version is checked to ensure that the version number (the value of this TLV) is ‘0’ which represents v1.

Listing 8.11 The SNMP version TLV parser.

static int parseVersion ( )
{
  int size = 0, seglen;
  tlvStructType tlv;

  
  
   
    
  
  
  size = parseTLV(request.buffer, request.index, &tlv);

  
  
   
    
  
  
  if (!((request.buffer[tlv.start] == INTEGER) && 
        (request.buffer[tlv.vstart] == SNMP_V1))) return -1;

  
  
   
    
  
  
  seglen = tlv.nstart - tlv.start;
  size += seglen;
  COPY_SEGMENT(tlv);
  size = parseCommunity();

  
  
   
    
  
  
  if (size == -1) return size;
  else return (size + seglen);
}

 

 

Once the VERSION TLV has been successfully parsed the next TLV parser is invoked(引用) for the Community string.

parseCommunity

The parseCommunity function parses the community string that is used as a security feature (安全特性)in SNMPv1 (but this should not be imply that SNMPv1 communication is secure). When a request is made of an SNMP agent, a string is inserted into the request. The SNMP agent looks at the string, and if it is not recognized, the agent simply discards the request. The string is commonly the word "public" but can be changed to represent anything else for your environment.

Listing 8.12 The SNMP community TLV parser.

static int parseCommunity ( )
{
  int ret = -1, seglen;
  tlvStructType community;
  int size=0;

  
  
   
    
  
  
  ret = parseTLV(request.buffer, request.index, &community);

  
  
   
    
  
  
  if (!((request.buffer[community.start] == OCTET_STRING) && 
        (community.len == COMMUNITY_SIZE))) return -1;

  
  
   
    
  
  
  if (!bcmp(&request.buffer[community.vstart], 
             (char *)COMMUNITY, COMMUNITY_SIZE)) {

  
  
   
    
  
  
    seglen = community.nstart - community.start;
    size += seglen;
    COPY_SEGMENT(community);

  
  
   
    
  
  
    size += parseRequest();

  
  
   
    
  
  
  } else {
    return -1;
  }

  
  
   
    
  
  
  return size;
}

 

 

After the TLV is parsed(解析) into the tlv structure, the type and length are checked (see Listing 8.9). The type should be OCTET_STRING (a character string) and the length should match(匹配) the length of the community string that we've configured into our agent. If these do not match, then we simply return an error. Otherwise we continue and check the request message community string with our configured string. We make this secondary check to minimize our processing of SNMPv1 PDUs that may be unauthorized(未经认证的). Once we match the community strings, we continue with our parse for the request-type.

parseRequest

The parseRequest function is a little more involved than prior(优先的) parsing functions (see Listing 8.13). This is because we'll be performing (执行)more functions than the typical single TLV parser. After parsing what should be the request (请求)TLV, we check its type to ensure it's a valid(正当的) request. We perform the standard (标准)copy to the response buffer and then change the response type to GET_RESPONSE (the result of all valid request PDUs). Next, we perform the basic TLV parsing of the request-id TLV, error-status TLV and error-index TLV. Since there is no error checking done on these values, their parsing (and response generation(回应产生)) is inserted here.

Listing 8.13 The SNMP request TLV parser.

static int parseRequest ( )
{
  int ret = -1, seglen;
  tlvStructType snmpreqsnmp请求), requested(请求id, errStatus(错误状态), errIndex(错误索引);
  int size = 0, respLoc, reqType;

  
  
   
    
  
  
  ret = parseTLV(request.buffer, request.index, &snmpreq);

  
  
   
    
  
  
  reqType = request.buffer[snmpreq.start];

  
  
   
    
  
  
  if ( !VALID_REQUEST(reqType) ) return -1;

  
  
   
    
  
  
  seglen = snmpreq.vstart - snmpreq.start;
    
    
seglen表示段的长度)
   
   
  respLoc = snmpreq.start;
  size += seglen;
  COPY_SEGMENT(snmpreq);

  
  
   
    
  
  
  response.buffer[snmpreq.start] = GET_RESPONSE;
//回应的buffer
  parseTLV(request.buffer, request.index, &requestid);

  
  
   
    
  
  
  seglen = requestid.nstart - requestid.start;

  
  
   
    
  
  
  size += seglen;
  COPY_SEGMENT(requestid);

  
  
   
    
  
  
  parseTLV(request.buffer, request.index, &errStatus);
  seglen = errStatus.nstart - errStatus.start;
  size += seglen;
  COPY_SEGMENT(errStatus);

  
  
   
    
  
  
  parseTLV(request.buffer, request.index, &errIndex);
  seglen = errIndex.nstart - errIndex.start;
  size += seglen;
  COPY_SEGMENT(errIndex);

  
  
   
    
  
  
  ret = parseSequenceOf(reqType);
  if (ret == -1) return -1;
  else size += ret;

  
  
   
    
  
  
  insertRespLen(snmpreq.start, respLoc, size);
//插入长度
   
   
  /* Store the error status and index, in the event an 
   * error was found 
   */
  if (errorStatus) {
    response.buffer[errStatus.vstart] = errorStatus;
    response.buffer[errIndex.vstart] = errorIndex + 1;
  }
  
  return size;
}

 

 

We then continue to parse(解析) the Sequence-Of TLV that is the last(在我们开始变量绑定解析之前的随后一个TLV TLV before we begin variable binding parsing. Note that when we complete parsing(结束解析) and return to this function, we not only insert the length into the response TLV but also error information. Since we've parsed the error-status and error-index TLVs, we manage (更新)updating the response buffer(相应缓冲) for these values at this level. The error information(错误信息) is generated while we parse(解析) the variable bindings(变量绑定), so this level of the parse is a good time to fill them in. The error-status simply indicates(表明) whether any errors occurred during parse of the variable bindings. The error-index indicates which of the variable bindings resulted in(导致) an error. These variables will have already been identified(支持、认出) at return of parseSequenceOf.

parseSequenceOf

The parseSequenceOf function is a basic TLV parser(基本的TLV解析器) with some error checking to ensure that we are looking at what we expect at the request TLV (see Listing 8.14). The major difference here is that we include the capability to call the next parser function multiple times. Remember that at this level a Sequence-Of is included to encapsulate(封装) all variable bindings that follow. Multiple variable bindings may be included, so this parser calls the parsesequence TLV parser as many times as is necessary. Size is also accumulated(积累) to update(更新) the length of the response at this level.

Listing 8.14 The SNMP sequence-of TLV parser.

static int parseSequenceOf ( int reqType )
{
  int ret = -1, seglen;
  tlvStructType seqof;
  int size = 0, respLoc;
  int index = 0;

  
  
   
    
  
  
  ret = parseTLV(request.buffer, request.index, &seqof);
//缓冲区,
  if ( request.buffer[seqof.start] != SEQUENCE_OF ) return -1;

  
  
   
    
  
  
  seglen = seqof.vstart - seqof.start;
//区域长度
  respLoc = response.index;
//回应索引
  COPY_SEGMENT(seqof);

  
  
   
    
  
  
  while (request.index < request.len) {
//索引
    size += parseSequence( reqType, index++ );
  }

  
  
   
    
  
  
  insertRespLen(seqof.start, respLoc, size);

  
  
   
    
  
  
  return size;
}

 

 

parseSequence

The parseSequence function is the start of the variable binding parser (see Listing 8.13). Remember that a variable binding is simply a construct that binds an OID (object ID) to a value. This results in three TLVs, one for the sequence, one for the variable (the OID) and one for the value. The parseSequence function simply parses the encapsulating sequence TLV and then calls the parseVarBind parser(解析器) to extract (抽取出)the OID and value TLVs.

Listing 8.15 The SNMP sequence(连续的) TLV parser.

static int parseSequence ( int reqType, int index )
{
  int ret = -1, seglen;
  tlvStructType seq;
  int size = 0, respLoc;

  
  
   
    
  
  
  ret = parseTLV(request.buffer, request.index, &seq);

  
  
   
    
  
  
  if ( request.buffer[seq.start] != SEQUENCE ) return -1;

  
  
   
    
  
  
  seglen = seq.vstart - seq.start;
  respLoc = response.index;
  COPY_SEGMENT(seq);

  
  
   
    
  
  
  size = parseVarBind( reqType, index );
  insertRespLen(seq.start, respLoc, size);
  size += seglen;

  
  
   
    
  
  
  return size;//返回数量
}

 

 

parseVarBind

The parseVarBind function is the most complex of all of the parser functions (see Listing 8.16). After the OID TLV is parsed into the variable name, we check to make sure that it is an OBJECT_IDENTIFIER(对象定义) TLV and then check our SNMP MIB table to see if it's present. We don't abort here if we can't find the OID because we have to complete the parse of this variable binding in order to build a correct response.(合理的回应)

We next check the request-type(返回类型) (stored previously(先前的) at the request parse(请求解析)). If we're dealing with a GET_REQUEST() or SET_REQUEST then we simply copy the OID TLV to the response and continue to the value. If we're dealing with a GET_NEXT_REQUEST, then we check to see if the SNMP MIB table contains an OID that follows this OID in the request PDU. As the name suggests, GET_NEXT_REQUEST takes an OID and returns the value (and name) of the next element in the SNMP MIB table. This function is useful(有用) for traversing a MIB table when the actual(实际的) contents(内容) may not be known. If the OID is not found in the table, we simply copy the request OID into the response and note that an error occurred. Otherwise, we call the getOID function which copies the next OID found in the SNMP MIB table into the response buffer so that the caller knows which OID is returned in the GET_NEXT_REQUEST response.

Once the OID has been parsed(解析), we move onto the value. We again use the parseTLV function to do our basic parse of the request into the value TLV. We now check to see if the OID was actually found in our SNMP MIB table. If not, we copy the request value to the response (which will be a NULL TLV) and then set the error index and error status to represent the error (NO_SUCH_NAME). If the OID was found, we either get the value of the OID from the SNMP table and load it into the response or in the case of a SET_REQUEST we take the value from the request PDU and store it into the SNMP MIB table.snmp mib表)

Finally we update the size and return to the parseSequence parser. The call chain then either moves forward to parse another variable binding within the loop, or returns through the call chain, updating lengths in the response PDU and ultimately resulting in a correct SNMP response message being returned to the sender.

Listing 8.16 The SNMP variable-binding parser.

static int parseVarBind ( int reqType, int index )
{
  int ret = -1, seglen = 0, id;
  tlvStructType name, value;
  int size = 0;

  
  
   
    
  
  
  extern const int maxData;

  
  
   
    
  
  
  ret = parseTLV(request.buffer, request.index, &name);

  
  
   
    
  
  
  if (request.buffer[name.start] != OBJECT_IDENTIFIER) return -1;
//
  id = findEntry(&request.buffer[name.vstart], name.len);

  
  
   
    
  
  
  /* For normal GET_REQUEST/SET_REQUEST, we simply copy the NAME 
   * tlv over and continue.  But for GET_NEXT_REQUEST, we need to 
   * identify the next OID and then copy it in as if it was the 
   * requested object(请求对象).
   */

  
  
   
    
  
  
  if ((reqType == GET_REQUEST) || (reqType == SET_REQUEST)) {
    seglen = name.nstart - name.start;
    COPY_SEGMENT(name);
    size = seglen;
  } else if (reqType == GET_NEXT_REQUEST) {

  
  
   
    
  
  
    response.buffer[response.index] = request.buffer[name.start];

  
  
   
    
  
  
    if (++id >= maxData) {
      id = OID_NOT_FOUND;
      seglen = name.nstart - name.start;
      COPY_SEGMENT(name);
      size = seglen;
    } else {
      /* Skip the name TLV */
      request.index += name.nstart - name.start;

  
  
   
    
  
  
      getOID(id, &response.buffer[response.index+2], 
              &response.buffer[response.index+1]);
  
      seglen = response.buffer[response.index+1]+2;
      response.index += seglen ;
      size = seglen;
    }

  
  
   
    
  
  
  }

  
  
   
    
  
  
  /* Parse the value TLV, but then replace with ours if we 
   * have it 
   */
  ret = parseTLV(request.buffer, request.index, &value);

  
  
   
    
  
  
  if (id != OID_NOT_FOUND) {
    unsigned char dataType;
    int len;

  
  
   
    
  
  
    if ((reqType == GET_REQUEST) || 
        (reqType == GET_NEXT_REQUEST)) {

  
  
   
    
  
  
      getEntry(id, &dataType, //获得入口
                &response.buffer[response.index+2], &len);

  
  
   
    
  
  
      response.buffer[response.index] = dataType;
      response.buffer[response.index+1] = len;
      seglen = (2 + len);
      response.index += seglen;

  
  
   
    
  
  
      /* Skip the NULL TLV in the request stream */
      request.index += (value.nstart - value.start);

  
  
   
    
  
  
    } else if (reqType == SET_REQUEST) {

  
  
   
    
  
  
      setEntry(id, &request.buffer[value.vstart], value.len, 
                   request.buffer[value.start], index);
      seglen = value.nstart - value.start;
      COPY_SEGMENT(value);

  
  
   
    
  
  
    }

  
  
   
    
  
  
  } else {

  
  
   
    
  
  
    seglen = value.nstart - value.start;
    COPY_SEGMENT(value);

  
  
   
    
  
  
    errorIndex = index;
    errorStatus = NO_SUCH_NAME;

  
  
   
    
  
  
  }

  
  
   
    
  
  
  size += seglen;

  
  
   
    
  
  
  return size;
}

Summary

In this chapter, we’ve looked at the SNMP application layer protocol and an implementation of a simple SNMP agent suitable for integration into an embedded system. We’ve discussed how the SNMP can be extended to support non-traditional applications (such as a remote antenna) and demonstrated(显示) how the provided implementation can be configured(配置,使成形).

This protocol may seem somewhat complicated given we're just moving simple data between two systems, but the idea to remember is that SNMP is the standard for managing and monitoring network equipment. Network management systems can realistically(可行的,现实的) manage large numbers of network assets(设备) in a uniform way(同意的方式). Having the ability to integrate into (整合)this existing widespread architecture(广泛的结构) can therefore be very valuable(珍贵的) for an embedded system design.

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值