To illustrate more accurately how SNMP actually works on the wire, lets look at sniffed packets captured during an SNMP GET. Only 2 packets are exchanged, a request by the manager and a response by the agent. The following was gathered using Ethereal:
No. Time Source Destination Protocol Info 112 1.936155 10.10.1.110 10.10.1.224 SNMP GET SNMP... Simple Network Management Protocol Version: 1 (0) Community: public PDU type: GET (0) Request Id: 0x2a7ee1af Error Status: NO ERROR (0) Error Index: 0 Object identifier 1: 1.3.6.1.2.1.1.4.0 (SNMPv2-MIB::sysContact.0) Value: NULL No. Time Source Destination Protocol Info 113 1.943425 10.10.1.224 10.10.1.110 SNMP RESPONSE SN... Simple Network Management Protocol Version: 1 (0) Community: public PDU type: RESPONSE (2) Request Id: 0x2a7ee1af Error Status: NO ERROR (0) Error Index: 0 Object identifier 1: 1.3.6.1.2.1.1.4.0 (SNMPv2-MIB::sysContact.0) Value: STRING: Ben Rockwood
Each packet is referred to as a PDU in SNMP speak. The first packet (with the Ethernet, IP, and UDP headers removed) is a version 1 GET request with an OID to get and a community name of "public". Notice that the GET PDU has a Value field, but its NULL. The second packet is the response which you'll notice is identical to the GET PDU with 2 exceptions: the PDU type is RESPONSE instead of GET and the Value field is populated.
Given this example you can see that when you make a request you hand a PDU to an agent and say "Please fill this in".
Putting this into the context of the Net-SNMP C API, look at the snmp_PDU structure.
typedef struct snmp_pdu { long version; int command; long reqid; ... long errstat; long errindex; ... netsnmp_variable_list *variables; ... u_char *community; ...
The version value maps directly the value seen in the packet above. The command value is the PDU type as seen in the packet above. And you can also see the reqid in the packets above as the Request ID. The important point here is that when a PDU structure is manipulated using the API your actually preparing an SNMP packet for transmission.
Forming a request, such as a GET, can be done in several ways. One method is to create a single PDU per OID you wish to request. As we saw above this would mean sending one packet per OID and thus receiving one packet for each request. Another method, which is completely valid, is to pack a single GET PDU with multiple OIDs for retrieval. The following is a break capture of such an exchange as seen on the wire:
No.Time Source Destination Protocol Info 28 2.240789 10.10.1.110 10.10.1.224 SNMP GET SNMPv... Simple Network Management Protocol Version: 1 (0) Community: public PDU type: GET (0) Request Id: 0x30549f5c Error Status: NO ERROR (0) Error Index: 0 Object identifier 1: 1.3.6.1.4.1.318.1.1.1.1.1.1.0 (SNMP... Value: NULL Object identifier 2: 1.3.6.1.4.1.318.1.1.1.1.2.3.0 (SNMP... Value: NULL No.Time Source Destination Protocol Info 41 2.328751 10.10.1.224 10.10.1.110 SNMP RESPONSE SNM... Simple Network Management Protocol Version: 1 (0) Community: public PDU type: RESPONSE (2) Request Id: 0x30549f5c Error Status: NO ERROR (0) Error Index: 0 Object identifier 1: 1.3.6.1.4.1.318.1.1.1.1.1.1.0 (SNMP... Value: STRING: "Silcon DP340E" Object identifier 2: 1.3.6.1.4.1.318.1.1.1.1.2.3.0 (SNMP... Value: STRING: "SE0XXXXXXX "
This exchange looks identical to the previous exchange but this time we've packed multiple (8 total, but I only show 2 for brevity) OIDs into the PDU. Hence, we send one packet and we are returned one packet.
Looking back at the PDU structure, it should not be obvious why there is no fixed value for the OID but instead an included netsnmp_variable_list pointer. This variable list is otherwise referred to as a VARLIST or VARBIND.
typedef struct variable_list netsnmp_variable_list; struct variable_list { struct variable_list *next_variable; oid *name; size_t name_length; u_char type; netsnmp_vardata val; size_t val_len; oid name_loc[MAX_OID_LEN]; u_char buf[40]; void *data; void (*dataFreeHook)(void *); int index; };
So looking at the variable list we immediately notice that its a linked list, hence the ability to pack multiple values into a single OID.
We can pull all this information together to get a much clearer idea of how the API really works. As seen in the captures what we really need to do is create, send, receive and then process one or more PDUs; thats the basics. In order to do this we need to initialize a PDU structure, append one or more variables into the variable list and then send that PDU out. What we'll get back is a fully populated (we hope) PDU, but this time each OID in the variable list will have a value referenced by the data pointer.
Next lets look at the actual code.