Example tested with SnmpSharpNet version 0.7.3.
This example shows how to use Socket class with async receive functionality in a WinForms GUI application without network causing negative impact on GUI operation. This example will correctly parse and display information in SNMPv1-TRAP, SNMPV2-TRAP and SNMPV2-INFORM format.
Critical methods in this example are Socket.BeginReceiveFrom(), Socket.EndReceiveFrom()
Example is a very simple application containing a check box (displayed as on/off button) that allows users to enable or disable SNMP trap/inform reception and a list box that dumps trap, inform and error information.
using System; using System.Windows.Forms; using System.Net; using System.Net.Sockets; using SnmpSharpNet; namespace AsyncTrapReceiver { public class Form1 : Form { protected Socket _socket; protected byte[] _inbuffer; protected IPEndPoint _peerIP; private System.Windows.Forms.ListBox listBox1; private System.Windows.Forms.CheckBox startCheckBox; public Form1 () { // it is not neccessary to initialize variables to null, but better safe then sorry _socket = null; this.listBox1 = new System.Windows.Forms.ListBox (); this.startCheckBox = new System.Windows.Forms.CheckBox (); this.SuspendLayout (); // // listBox1 // this.listBox1.Anchor = ((System.Windows.Forms.AnchorStyles) ((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); this.listBox1.FormattingEnabled = true; this.listBox1.Location = new System.Drawing.Point (13, 13); this.listBox1.Name = "listBox1"; this.listBox1.Size = new System.Drawing.Size (328, 368); this.listBox1.TabIndex = 0; // // startCheckBox // this.startCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles) ((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); this.startCheckBox.Appearance = System.Windows.Forms.Appearance.Button; this.startCheckBox.Location = new System.Drawing.Point (347, 12); this.startCheckBox.Name = "startCheckBox"; this.startCheckBox.Size = new System.Drawing.Size (75, 24); this.startCheckBox.TabIndex = 3; this.startCheckBox.Text = "&Start"; this.startCheckBox.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; this.startCheckBox.UseVisualStyleBackColor = true; this.startCheckBox.CheckedChanged += new System.EventHandler (this.onStartChanged); // // Form1 // this.AutoScaleDimensions = new System.Drawing.SizeF (6f, 13f); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size (434, 391); this.Controls.Add (this.startCheckBox); this.Controls.Add (this.listBox1); this.Name = "Form1"; this.Text = "Form1"; this.ResumeLayout (false); } public bool InitializeReceiver () { if (_socket != null) { StopReceiver (); } try { // create an IP/UDP socket _socket = new Socket (AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); } catch (Exception ex) { listBox1.Items.Add ("SNMP trap receiver socket initialization failed with error: " + ex.Message); // there is no need to close the socket because it was never correctly created _socket = null; } if (_socket == null) return false; try { // prepare to "bind" the socket to the local port number // binding notifies the operating system that application // wishes to receive data sent to the specified port number // prepare EndPoint that will bind the application to all available //IP addresses and port 162 (snmp-trap) EndPoint localEP = new IPEndPoint (IPAddress.Any, 162); // bind socket _socket.Bind (localEP); } catch (Exception ex) { listBox1.Items.Add ("SNMP trap receiver initialization failed with error: " + ex.Message); _socket.Close (); _socket = null; } if (_socket == null) return false; if (!RegisterReceiveOperation ()) return false; return true; } public void StopReceiver () { if (_socket != null) { _socket.Close (); _socket = null; } } public bool RegisterReceiveOperation () { if (_socket == null) return false; // socket has been closed try { _peerIP = new IPEndPoint (IPAddress.Any, 0); // receive from anybody EndPoint ep = (EndPoint)_peerIP; _inbuffer = new byte[64 * 1024]; // nice and big receive buffer _socket.BeginReceiveFrom (_inbuffer, 0, 64 * 1024, SocketFlags.None, ref ep, new AsyncCallback (ReceiveCallback), _socket); } catch (Exception ex) { listBox1.Items.Add ("Registering receive operation failed with message: " + ex.Message); _socket.Close (); _socket = null; } if (_socket == null) return false; return true; } private void ReceiveCallback (IAsyncResult result) { // get a reference to the socket. This is handy if socket has been closed elsewhere in the class Socket sock = (Socket)result.AsyncState; _peerIP = new IPEndPoint (IPAddress.Any, 0); // variable to store received data length int inlen; try { EndPoint ep = (EndPoint)_peerIP; inlen = sock.EndReceiveFrom (result, ref ep); _peerIP = (IPEndPoint)ep; } catch (Exception ex) { // only post messages if class socket reference is not null // in all other cases, user has terminated the socket if (_socket != null) { PostAsyncMessage ("Receive operation failed with message: " + ex.Message); } inlen = -1; } // if socket has been closed, ignore received data and return if (_socket == null) return; // check that received data is long enough if (inlen <= 0) { // request next packet RegisterReceiveOperation (); return; } int packetVersion = SnmpPacket.GetProtocolVersion (_inbuffer, inlen); if (packetVersion == (int)SnmpVersion.Ver1) { SnmpV1TrapPacket pkt = new SnmpV1TrapPacket (); try { pkt.decode (_inbuffer, inlen); } catch (Exception ex) { PostAsyncMessage ("Error parsing SNMPv1 Trap: " + ex.Message); pkt = null; } if (pkt != null) { PostAsyncMessage (String.Format ("** SNMPv1 TRAP from {0}", _peerIP.ToString ())); PostAsyncMessage ( String.Format ("*** community {0} generic id: {1} specific id: {2}", pkt.Community, pkt.Pdu.Generic, pkt.Pdu.Specific) ); PostAsyncMessage (String.Format ("*** PDU count: {0}", pkt.Pdu.VbCount)); foreach (Vb vb in pkt.Pdu.VbList) { PostAsyncMessage ( String.Format ("**** Vb oid: {0} type: {1} value: {2}", vb.Oid.ToString (), SnmpConstants.GetTypeName (vb.Value.Type), vb.Value.ToString ()) ); } PostAsyncMessage ("** End of SNMPv1 TRAP"); } } else if (packetVersion == (int)SnmpVersion.Ver2) { SnmpV2Packet pkt = new SnmpV2Packet (); try { pkt.decode (_inbuffer, inlen); } catch (Exception ex) { PostAsyncMessage ("Error parsing SNMPv1 Trap: " + ex.Message); pkt = null; } if (pkt != null) { if (pkt.Pdu.Type == PduType.V2Trap) { PostAsyncMessage (String.Format ("** SNMPv2 TRAP from {0}", _peerIP.ToString ())); } else if (pkt.Pdu.Type == PduType.Inform) { PostAsyncMessage (String.Format ("** SNMPv2 INFORM from {0}", _peerIP.ToString ())); } else { PostAsyncMessage (String.Format ("Invalid SNMPv2 packet from {0}", _peerIP.ToString ())); pkt = null; } if (pkt != null) { PostAsyncMessage ( String.Format ("*** community {0} sysUpTime: {1} trapObjectID: {2}", pkt.Community, pkt.Pdu.TrapSysUpTime.ToString (), pkt.Pdu.TrapObjectID.ToString ()) ); PostAsyncMessage (String.Format ("*** PDU count: {0}", pkt.Pdu.VbCount)); foreach (Vb vb in pkt.Pdu.VbList) { PostAsyncMessage ( String.Format ("**** Vb oid: {0} type: {1} value: {2}", vb.Oid.ToString (), SnmpConstants.GetTypeName (vb.Value.Type), vb.Value.ToString ()) ); } if (pkt.Pdu.Type == PduType.V2Trap) PostAsyncMessage ("** End of SNMPv2 TRAP"); else { PostAsyncMessage ("** End of SNMPv2 INFORM"); // send ACK back to the INFORM sender SnmpV2Packet response = pkt.BuildInformResponse (); byte[] buf = response.encode (); _socket.SendTo (buf, (EndPoint)_peerIP); } } } } RegisterReceiveOperation (); } protected delegate void PostAsyncMessageDelegate (string msg); protected void PostAsyncMessage (string msg) { if (InvokeRequired) Invoke (new PostAsyncMessageDelegate (PostAsyncMessage), new object[] { msg }); else listBox1.Items.Add (msg); } private void onStartChanged (object sender, EventArgs e) { if (startCheckBox.Checked) { if (!InitializeReceiver ()) { // unable to start TRAP receiver startCheckBox.Checked = false; return; } else { startCheckBox.Text = "S&top"; } } else { StopReceiver (); startCheckBox.Text = "&Start"; } } } }
Imports System Imports System.Windows.Forms Imports System.Net Imports System.Net.Sockets Imports SnmpSharpNet Public Class Form1 Inherits Form Dim _socket As Socket Dim _inbuffer() As Byte Dim _peerIP As IPEndPoint Dim listBox1 As System.Windows.Forms.ListBox Dim WithEvents startCheckBox As System.Windows.Forms.CheckBox Public Sub New() System.Diagnostics.Debug.WriteLine("OnLoad event") _socket = Nothing listBox1 = New System.Windows.Forms.ListBox() startCheckBox = New System.Windows.Forms.CheckBox() SuspendLayout() ' ' listBox1 ' listBox1.Parent = Me listBox1.Anchor = ((((System.Windows.Forms.AnchorStyles.Top Or _ System.Windows.Forms.AnchorStyles.Bottom) Or _ System.Windows.Forms.AnchorStyles.Left) Or _ System.Windows.Forms.AnchorStyles.Right)) listBox1.FormattingEnabled = True listBox1.Location = New System.Drawing.Point(13, 13) listBox1.Name = "listBox1" listBox1.Size = New System.Drawing.Size(328, 368) listBox1.TabIndex = 0 ' ' startCheckBox ' startCheckBox.Parent = Me startCheckBox.Anchor = (System.Windows.Forms.AnchorStyles.Top Or _ System.Windows.Forms.AnchorStyles.Right) startCheckBox.Appearance = System.Windows.Forms.Appearance.Button startCheckBox.Location = New System.Drawing.Point(347, 12) startCheckBox.Name = "startCheckBox" startCheckBox.Size = New System.Drawing.Size(75, 24) startCheckBox.TabIndex = 3 startCheckBox.Text = "&Start" startCheckBox.TextAlign = System.Drawing.ContentAlignment.MiddleCenter startCheckBox.UseVisualStyleBackColor = True ' ' Form1 ' AutoScaleDimensions = New System.Drawing.SizeF(6.0F, 13.0F) AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font ClientSize = New System.Drawing.Size(434, 391) Me.Controls.Add(startCheckBox) Me.Controls.Add(listBox1) Name = "Form1" Text = "Form1" ResumeLayout(False) End Sub Public Function InitializeReceiver() As Boolean If _socket IsNot Nothing Then StopReceiver() End If Try _socket = New Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp) Catch ex As Exception listBox1.Items.Add("SNMP trap receiver socket initialization failed with error: " & ex.Message) ' there is no need to close the socket because it was never correctly created _socket = Nothing Return False End Try Try Dim ep As EndPoint = New IPEndPoint(System.Net.IPAddress.Any, 162) _socket.Bind(ep) Catch ex As Exception listBox1.Items.Add("SNMP trap receiver initialization failed with error: " & ex.Message) _socket.Close() _socket = Nothing Return False End Try If RegisterReceiveOperation() = False Then Return False End If Return True End Function Public Sub StopReceiver() If _socket IsNot Nothing Then _socket.Close() _socket = Nothing End If End Sub Public Function RegisterReceiveOperation() As Boolean If _socket Is Nothing Then Return False End If Try _peerIP = New IPEndPoint(System.Net.IPAddress.Any, 0) ' receive from anybody _inbuffer = New [Byte](64 * 1024) {} ' nice and big receive buffer _socket.BeginReceiveFrom(_inbuffer, 0, _inbuffer.Length, _ System.Net.Sockets.SocketFlags.None, _ _peerIP, New AsyncCallback(AddressOf ReceiveCallback), _socket) Catch ex As Exception listBox1.Items.Add("Registering receive operation failed with message: " & ex.Message) _socket.Close() _socket = Nothing Return False End Try Return True End Function Public Sub ReceiveCallback(ByVal result As IAsyncResult) Dim sock As Socket = result.AsyncState Dim inlen As Integer _peerIP = New IPEndPoint(System.Net.IPAddress.Any, 0) Try inlen = sock.EndReceiveFrom(result, _peerIP) Catch ex As Exception ' only post messages if class socket reference is not null ' in all other cases, user has terminated the socket If _socket IsNot Nothing Then PostAsyncMessage("Receive operation failed with message: " & ex.Message) End If inlen = -1 End Try ' if socket has been closed, don't process received data If _socket IsNot Nothing Then ' check that received data is long enough If inlen > 0 Then Dim packetVersion As Integer = SnmpPacket.GetProtocolVersion(_inbuffer, inlen) If packetVersion = SnmpVersion.Ver1 Then Dim pkt As SnmpV1TrapPacket = New SnmpV1TrapPacket Try pkt.decode(_inbuffer, inlen) Catch ex As Exception PostAsyncMessage("Error parsing SNMPv1 Trap: " + ex.Message) pkt = Nothing End Try If pkt IsNot Nothing Then PostAsyncMessage(String.Format("** SNMPv1 TRAP from {0}", _peerIP.ToString())) PostAsyncMessage(String.Format("*** community {0} generic id: {1} specific id: {2}", _ pkt.Community, pkt.Pdu.Generic, pkt.Pdu.Specific)) PostAsyncMessage(String.Format("*** PDU count: {0}", pkt.Pdu.VbCount)) Dim vb As Vb For Each vb In pkt.Pdu.VbList PostAsyncMessage(String.Format("**** Vb oid: {0} type: {1} value: {2}", _ vb.Oid.ToString(), SnmpConstants.GetTypeName(vb.Value.Type), _ vb.Value.ToString())) Next PostAsyncMessage("** End of SNMPv1 TRAP") End If ElseIf packetVersion = SnmpVersion.Ver2 Then Dim pkt As SnmpV2Packet = New SnmpV2Packet Try pkt.decode(_inbuffer, inlen) Catch ex As Exception pkt = Nothing End Try If pkt.Pdu.Type = PduType.V2Trap Then PostAsyncMessage(String.Format("** SNMPv2 TRAP from {0}", _peerIP.ToString())) ElseIf pkt.Pdu.Type = PduType.Inform Then PostAsyncMessage(String.Format("** SNMPv2 INFORM from {0}", _peerIP.ToString())) Else PostAsyncMessage(String.Format("Invalid SNMPv2 packet from {0}", _peerIP.ToString())) pkt = Nothing End If If pkt IsNot Nothing Then PostAsyncMessage(String.Format("*** community {0} sysUpTime: {1} trapObjectID: {2}", _ pkt.Community, pkt.Pdu.TrapSysUpTime.ToString(), pkt.Pdu.TrapObjectID.ToString())) PostAsyncMessage(String.Format("*** PDU count: {0}", pkt.Pdu.VbCount)) Dim vb As Vb For Each vb In pkt.Pdu.VbList PostAsyncMessage(String.Format("**** Vb oid: {0} type: {1} value: {2}", _ vb.Oid.ToString(), SnmpConstants.GetTypeName(vb.Value.Type), vb.Value.ToString())) Next If pkt.Pdu.Type = PduType.V2Trap Then PostAsyncMessage("** End of SNMPv2 TRAP") Else PostAsyncMessage("** End of SNMPv2 INFORM") ' send ACK back to the INFORM sender Dim response As SnmpV2Packet = pkt.BuildInformResponse() Dim buf() As Byte = response.encode() _socket.SendTo(buf, _peerIP) End If End If End If RegisterReceiveOperation() End If End If End Sub Protected Delegate Sub PostAsyncMessageDelegate(ByVal msg As String) Protected Sub PostAsyncMessage(ByVal msg As String) If InvokeRequired Then Dim param() As Object param = New Object() {msg} Invoke(New PostAsyncMessageDelegate(AddressOf PostAsyncMessage), param) Else listBox1.Items.Add(msg) End If End Sub Private Sub OnStartChanged(ByVal sender As Object, ByVal e As EventArgs) _ Handles startCheckBox.CheckedChanged If startCheckBox.Checked Then If Not InitializeReceiver() Then startCheckBox.Checked = False Else startCheckBox.Text = "S&top" End If Else StopReceiver() startCheckBox.Text = "&Start" End If End Sub End Class
If you are using SnmpSharpNet library to build advanced manager (or agent) functionality, you will need to use low level functions to craft individual packets. Low level access to the SNMP packets can be archived through version specific classes derived from SnmpPacket base class.
To manipulate SNMP version 1 packet, you will need to use SnmpV1Packet class.
Before you can start using the class, you need to understand the structure of the SNMP packet and the information required to assemble a valid packet. To begin understanding the packet format, we'll begin with the encoded packet layout.
SNMP version 1 packet is layed out as follows:
| Name | Type | Length | Description |
|---|---|---|---|
| Sequence | Sequence | variable | Packet sequence |
| Version | Integer32 | 1 byte | Protocol version number. For SNMPv1 this field is set to 0 |
| Community | OctetString | variable | SNMP community name |
| Pdu Sequence | Sequence | variable | Start of the Protocol Data Unit |
| RequestId | Integer32 | variable | Unique request id |
| ErrorStatus | Integer32 | variable | ErrorStatus of SNMP request. 0 = noError |
| ErrorIndex | Integer32 | variable | Index of the VarBind in the request that caused the error |
| Variable Bindings | Sequence | variable | Sequence of VarBind entries |
Variable Bindings format is:
| Field # | Name | Type | Description |
|---|---|---|---|
| 1 | VarBinds header | Sequence | Wrapper header around the entire sequence |
| 2 | VarBind header | Sequence | Wrapper around a single Variable Binding |
| 3 | ObjectID | Oid | ObjectID for the value in the Variable Binding |
| 4 | ASN Value | variable | Value associated with the ObjectID |
| ... | Fields 2 to 4 are repeated for every VarBind entry in the collection | ||
Here is an example WireShark decode of an SNMP version 1 packet:
SNMP version 1 packet capture
Based on the above information, information required to encode a valid SNMP request is:
Now that you know what information you need to create a valid request lets create a SnmpV1Packet:
SnmpV1Packet packet = new SnmpV1Packet(); // Set the community name packet.Community.Set("public"); // Set the Pdu type packet.Pdu.Type = PduType.Get; // Set request id packet.Pdu.RequestID = 100; // Add an Oid for the SNMP-Get operation packet.Pdu.VbList.Add(".1.3.6.1.2.1.1.1.0");
This example shows how to prepare a SNMP-Get request. By default, when calling Pdu.VbList.Add(string) method, Oid is parsed from the string and value in the VariableBinding is set to Null.
GetNext request is identical except for setting the Pdu type to PduType.GetNext.
SET request is different because you have to set both the Oid and value to set the Oid to on the agent. Here is what you would change for an SNMP-Set request:
// Set the Pdu type packet.Pdu.Type = PduType.Set; // Add an Oid and Value for the SNMP-Set operation packet.Pdu.VbList.Add(new Oid(".1.3.6.1.2.1.1.1.0"), new OctetString("New sysDescr.0 value"));
SNMP version 1 class is now ready to be BER encoded for transmission:
byte[] outBuffer = packet.encode();
That is it! outBuffer byte array now holds the BER encoded SNMP version 1 packet that is ready for transmission to an agent. Just call Socket.Send or Socket.SendTo to send the request.
Now that you have sent a request, what do you do when you receive a reply? Nothing easier, assuming you have received a reply from the agent (you should check IP address and port number of the sender to make sure it's from the expected device) you need to decode it. In this example, I assume you received the packet into a pre-defined byte array inBuffer and that received data length is stored in inLength variable:
SnmpV1Packet result = new SnmpV1Packet(); result.decode(inBuffer, inLength);
or you can choose to reuse the class you created to send the request:
packet.Reset(); packet.decode(inBuffer, inLength);
From this point, data is available to you in the usable format. You should compare the SNMP community name and request id to confirm they match your request and then process the information returned by the agent.
Both SnmpV1Packet encode and decode methods will throw Exceptions if an error is encountered. Please check library documentation for details of Exceptions that can be thrown.
Using the packet class to manipulate SNMP version 2c packet is not too different from manipulating SNMP version 1 packets.
To manipulate a SNMP version 2 packet, you will need to use SnmpV2Packet class.
Layout of the packet is identical to the SNMP version 1 packet format so I will not go into it (again).
Lets focus instead on what information you need encode a valid SNMP version 2 request:
See, exactly the same as SNMP version 1 !!
Now that you know what information you need to create a valid request lets create a SnmpV2Packet:
SnmpV2Packet packet = new SnmpV2Packet(); // Set the community name packet.Community.Set("public"); // Set the Pdu type packet.Pdu.Type = PduType.Get; // Set request id packet.Pdu.RequestID = 100; // Add an Oid for the SNMP-Get operation packet.Pdu.VbList.Add(".1.3.6.1.2.1.1.1.0");
This example shows how to prepare a SNMP-Get request. By default, when calling Pdu.VbList.Add(string) method, Oid is parsed from the string and value in the VariableBinding is set to Null.
GetNext request is identical except for setting the Pdu type to PduType.GetNext.
SET request is different because you have to set both the Oid and value to set the Oid to on the agent. Here is what you would change for an SNMP-Set request:
// Set the Pdu type packet.Pdu.Type = PduType.Set; // Add an Oid and Value for the SNMP-Set operation packet.Pdu.VbList.Add(new Oid(".1.3.6.1.2.1.1.1.0"), new OctetString("New sysDescr.0 value"));
SNMP version 2 class is now ready to be BER encoded for transmission:
byte[] outBuffer = packet.encode();
That is it! outBuffer byte array now holds the BER encoded SNMP version 2 packet that is ready for transmission to an agent. Just call Socket.Send or Socket.SendTo to send the request.
Now that you have sent a request, what do you do when you receive a reply? Nothing easier, assuming you have received a reply from the agent (you should check IP address and port number of the sender to make sure it's from the expected device) you need to decode it. In this example, I assume you received the packet into a pre-defined byte array inBuffer and that received data length is stored in inLength variable:
SnmpV2Packet result = new SnmpV2Packet(); result.decode(inBuffer, inLength);
or you can choose to reuse the class you created to send the request:
packet.Reset(); packet.decode(inBuffer, inLength);
From this point, data is available to you in the usable format. You should compare the SNMP community name and request id to confirm they match your request and then process the information returned by the agent.
Trap processing in SNMP version 2 has considerably changed. In SNMP version 1, trap has a different packet format from other SNMP packets and requires special processing. For that reason I had to create a dedicated SnmpV1TrapPacket class to handle processing of traps with the version 1 of the protocol. This has changed in SNMP version 2 and new Trap format, known as V2Trap is adopted. V2Trap has the same format as any other SNMP version 2 packet. There are 2 requirements that need to be met to construct a valid V2Trap packet. First Oid/Value pair in the Variable Bindings collection has to be TrapSysUpTime.0 and second has to be TrapObjectID.0.
When SnmpV2Packet class parses a received V2Trap packet, two required Oid/Value pairs are parsed out of the Variable Bindings collection and removed from it. You can access the values using SnmpV2Packet.Pdu.TrapSysUpTime and SnmpV2Packet.Pdu.TrapObjectID properties.
Other then the two additional properties, V2Trap is the same as any other SNMP v2 packet:
SnmpV2Packet packet = new SnmpV2Packet(); // Decode received packet packet.decode(inBuffer, inLength); // Make sure it is a V2Trap if( packet.Pdu.Type == PduType.V2Trap ) { // Process information in the packet Console.WriteLine("Received trap {0} at agent upTime {1}", packet.Pdu.TrapObjectID.ToString(), packet.Pdu.TrapSysUpTime.ToString()); foreach( Vb v in packet.Pdu.VbList ) { Console.WriteLine("\t{0}: {1} {2}", v.Oid.ToString(), SnmpConstants.GetTypeName(v.Value.Type), v.Value.ToString()); } }
There is one new request type, GetBulk and one completely new notification type, Inform that are introduced in the SNMP version 2.
To use SNMP version 2 GetBulk request type you will need to set two new SnmpV2Packet class properties, NonRepeaters and MaxRepetitions.
GetBulk request performs multiple GetNext operations on the requested Oids and returns them in a single response. MaxRepetitions property tells the agent how many GetNext operations to perform on each requested Oid. NonRepeaters tells the agent to perform a single GetNext on the specified number of Oids from the beginning of the Variable Bindings collection.
By default, SnmpV2Packet class sets NonRepeaters value to zero and MaxRepetitions value to 100. You can change these values to any value from 0 to 255. Setting MaxRepetitions property to 1 will make GetBulk request perform like GetNext.
Here are the changes you need to make to the above example to construct a GetBulk request:
Both SnmpV2Packet encode and decode methods will throw Exceptions if an error is encountered. Please check library documentation for details of Exceptions that can be thrown.
// Set the Pdu type packet.Pdu.Type = PduType.GetBulk; // Add Oids to the request packet.Pdu.VbList.Add(".1.3.6.1.2.1.1.3"); // sysUpTime packet.Pdu.VbList.Add(".1.3.6.1.2.1.2.2.1.2"); // ifDescr // Get 1 value for the first Oid, that will be sysUpTime (.1.3.6.1.2.1.1.3.0) packet.Pdu.NonRepeaters = 1; // Retrieve up to 50 ifDescr interface description values in a single request packet.Pdu.MaxRepetitions = 50;
When working with the SnmpV2Packet class, make sure you don't attempt to access MaxRepetitions and NonRepeaters properties on packets that are Type other then GetBulk because you will receive SnmpInvalidPduTypeException if you do. Same is true if you attempt to access ErrorStatus and ErrorIndex properties in GetBulk packet class.
You can think of Inform notifications as acknowledged Traps. When an agent sents an Inform, you are supposed to receive it, process it and send back acknowledgement that you received it.
Since the primary purpose of the library is to help build manager applications, receiving Inform packets and constructing responses/acknowledgement packets is made to be as easy as possible. Here is an example of how to receive an Inform and send back an ack:
In this example we assume that incoming packet was received into a predefined inBuffer byte array and that length of the received data in stored in a variable inLength
// Construct the packet class. SnmpV2Packet packet = new SnmpV2Packet(); // Decode received information. packet.decode(inBuffer, inLength); // Verify we received an Inform packet. if( packet.Pdu.Type == PduType.Inform ) { // Construct appropriate response to the Inform. SnmpV2Packet response = packet.BuildInformResponse(); // BER encode response packet. byte[] outBuffer = response.encode(); // Send response back to the Inform sender. mysocket.SendTo(outBuffer,new IPEndPoint(someip, someport)); }
SNMP operations for version 3 are inherited from the protocol version 2. SNMP version 3 supports Get, GetNext, GetBulk, Set, Response, V2Trap and Inform requests, responses and notifications.
New features in SNMP version 3 offer new security functions not available in previous versions of the protocol.
Security features include:
Additional to the above security features, additional security functionality is available, for example VACL, which are not covered in this document but are supported by the SnmpSharpNet library.
This document assumes that you are familiar with concepts outlined in the SNMP version 3 Concepts document and is focused on manager application development.
The information you are going to need for every SNMP version 3 request, regardless of the security model used, is:
Information above is the minimum information required to construct a valid noAuthNoPriv (no authentication and no privacy) request.
Minimum required information is stored in the User Security Model header. To better understand the packet format and information required to construct a valid request here is the packet format for SNMP version 3:
| Field # | Name | Data type | Description |
|---|---|---|---|
| 1 | Packet sequence | Sequence | First sequence wraps the entire SNMP packet. |
| 2 | Version | Integer32 | SNMP Protocol version number |
| 3 | Global data | Sequence | Global message data sequence |
| 4 | Message ID | Integer32 | Message id |
| 5 | MaxMsgSize | Integer32 | Maximum message size accepted by the host |
| 6 | MsgFlags | OctetString | Single byte message flags. |
| 7 | SecModel | Integer32 | Security model used in the message. USM = 3 |
| -- End of Global data sequence -- | |||
| 8 | AuthEngineID | OctetString | Authoritative SNMP EngineID |
| 9 | AuthEngineBoots | Integer32 | Authoritative SNMP Engine boots value |
| 10 | AuthEngineTime | Integer32 | Authoritative SNMP Engine time value |
| 11 | Secret (user) name | OctetString | Secret name |
| 12 | AuthParams | OctetString | Message authentication parameters |
| 13 | PrivParams | OctetString | Message privacy parameters |
| 14 | ScopedPdu | Sequence | SNMP version 3 scoped Pdu sequence |
| 15 | ContextEngineID | OctetString | Context engine ID |
| 16 | ContextName | OctetString | Context name |
| 17 | Pdu | Sequence | Standard Pdu compatible with SNMP v1 and v2 |
| 18 | RequestID | Integer32 | Unique request ID (not related to MessageID) |
| 19 | ErrorStatus | Integer32 | Error status code |
| 20 | ErrorIndex | Integer32 | Reference to the Vb entry in the Variable Bindings that caused the error |
| 21 | VarBinds | Sequence | Sequence around all Vb entries |
| 22 | Vb | Sequence | Vb sequence Oid/Value |
| 23 | Oid | Oid | Vb Oid |
| 24 | AsnValue | various | ASN.1 value associated with the Vb Oid |
| -- End of Vb -- Multiple Vb entries can be part of the VarBinds sequence | |||
| -- End of Var Binds -- | |||
| -- Packet sequence -- | |||
As you can see, SNMP version 3 packet is considerably more complex then its predecessors.
Now lets figure out what needs to be done to build a basic noAuthNoPriv request.
Before you can make requests, you need to have the SecretName (username) for the hosts you wish to query. Think of this as the equivelent of the SNMP community name in SNMP v1 and v2.
Security name is not enough. You will need authoritative agent engine information before you can make requests. To get this information, you will need to send a discovery request.
Discovery request is a request to the authoritative SNMP engine with AuthEngineID, AuthEngineBoots, AuthEngineTime, SecurityName, ContextEngineID and ContextName fields set to null (or 0 for Integer32 fields). Message flags has to have the Reportable bit set to 1 (true) to notify the SNMP agent that we wish to receive failure notifications (called Reports in SNMP v3 terminology).
To construct a discovery packet do the following:
// Create discovery packet using SnmpV3Packet.DiscoveryRequest() static method SnmpV3Packet discovery = SnmpV3Packet.DiscoveryRequest(); // BER encode discovery packet byte[] outBuffer = discovery.encode(); // Send discovery packet to the agent mysocket.SendTo(outBuffer, new IPEndPoint(agentIP, agentPort));
When reply is received (assuming received data is stored in inBuffer byte array and length of received data is stored in the inLength variable) following will verify discovery procedure was successful:
// Decode received packet discovery.decode(inBuffer, inLength); // Make sure received packet is SNMP v3 Report if( discovery.Version != SnmpVersion.Ver3 ) { return; // Invalid SNMP version } if( discovery.Pdu.Type != PduType.Report ) { return; // Invalid response. We have to get a Report } if( ! discovery.Pdu.VbList[0].Oid.Equals(SnmpConstants.usmStatsUnknownEngineIDs) ) { return; // Wrong kind of error message sent by agent } // Success !!
Returned Report from the SNMPv3 agent will contain authoritative EngineID, EngineBoots and EngineTime values. Timeliness check that is performed on every SNMPv3 packet is tied to EngineBoots and EngineTime. If request received by the agent contains SnmpBoots value that is not equal to the value on the Agent, request is rejected. Also, if SnmpTime is not within 150 seconds of the value on the agent, request is rejected.
There is not much you can do with the EngineBoots value. This value can be incremented because Agent was rebooted, SNMP stack on the agent was restarted or because EngineTime has reached maximum value and had to roll back to 0 (which is when SnmpBoots value has to be incremented by the agent. Because there are a lot of events that can cause EngineBoots value to change, there is not much point in worrying about it. Just store the value and reuse it in following requests.
EngineTime on the other hand is a value that you can track and increment according to the time that has elapsed between the discovery process and the time you are sending you request. Just storing the discovery time in a DateTime class will enable you to calculate the elapsed time.
You will know that time values (EngineBoots and EngineTime) are out of sync with the Agent if you receive a Report with Oid usmStatsNotInTimeWindows in the VarBinds. If this happens, you will need to perform an additional discovery to update the timeliness values.
Another handy value to retain is MaxMessageSize. This value is the maximum length (in bytes) that the Agent can process in a single request. You should make sure not to send requests to the agent that will exceed this size.
So let's store discovered values:
// Authoritative Engine ID OctetString engineID = (OctetString)discovery.USM.EngineId.Clone(); // Authoritative Engine Boots Int32 engineBoots = discovery.USM.EngineBoots; // Authoritative Engine Time Int32 engineTime = discovery.USM.EngineTime; // Timestamp when discovery process was completed DateTime discoveryTime = DateTime.Now; // Maximum message size agent can process Int32 maxMessageSize = discovery.MaxMessageSize;
With this information you are ready to construct a request. Here is how you do it:
// Request packet class SnmpV3Packet request = new SnmpV3Packet(); // Set security model to NoAuthNoPriv with SecurityName "milan" request.NoAuthNoPriv(ASCIIEncoding.UTF8.GetBytes("milan")); // Set authoritative engine ID retrieved during discovery request.SetEngineId(engineID); // Set timeliness values retrieved during discovery request.SetEngineTime(engineBoots, engineTime); // Set maximum message size request.MaxMessageSize = maxMessageSize; // Set Pdu type to Get request.ScopedPdu.Type = PduType.Get; // Add Oid to query to the VbList request.ScopedPdu.VbList.Add(".1.3.6.1.2.1.1.1.0"); // Encode request outBuffer = request.encode(); // Now you are ready to send the request to the agent
Before going any further, you should know what request.NoAuthNoPriv(byte[]) method does. This is just a helper method that sets flags (in SnmpV3Packet.MsgFlags) for Authentication and Privacy to false and sets the SecurityName (or user name) to the value specified. Comparable methods are available for authNoPriv and authPriv security modes that make generation of appropriately secured packets as easy as possible.
After sending the request and receiving a raw reply, you can parse it in two ways. One way is to reuse the packet class you used to send the request like this:
request.decode(inBuffer, inLength);
And data is decoded correctly. You can do the same with a new packet class when making noAuthNoPriv requests, like this:
// Create a response packet class SnmpV3Packet response = new SnmpV3Packet(); // Decode response response.decode(inBuffer, inLength); // Process response Console.WriteLine("Reply received to message id {0} request id {1}", response.MessageId, response.Pdu.RequestId); Console.WriteLine("\t{0}: {1} {2}", response.ScopedPdu.VbList[0].Oid.ToString(), SnmpConstants.GetTypeName(response.ScopedPdu.VbList[0].Value.Type), response.ScopedPdu.VbList[0].Value.ToString());
Introducing additional layers of security does not complicate coding too much. For example, to change the above example to support authNoPriv security mode, you would do the following:
request.authNoPriv(ASCIIEncoding.UTF8.GetBytes("milan"), ASCIIEncoding.UTF8.GetBytes("myAuthSecret"), AuthenticationDigests.MD5);
When decoding reply information from the agent, you will need to initialize SnmpV3Packet class with the correct authentication information for the verification of the received information to succeed. Reply from the agent processing will look like this:
// Create a response packet class SnmpV3Packet response = new SnmpV3Packet(); // Set authentication parameters to use with the incoming packet response.authNoPriv(ASCIIEncoding.UTF8.GetBytes("milan"), ASCIIEncoding.UTF8.GetBytes("myAuthSecret"), AuthenticationDigests.MD5); // Decode response response.decode(inBuffer, inLength);
If haven't noticed, there is a potential issue with the need to preset the authentication information in the packet prior to parsing received packets. In a situation where you send multiple requests to multiple agents, you will not know which security mode to apply to packets as they are coming back (assuming each agent or request could have a different mode). This is addressed with SnmpV3Packet.GetUSM(byte[],int) method.
GetUSM() method allows you to "look ahead" into the received byte buffer and parse only the header information, up to and including User Security Model header information. When you call this method, information that will be parsed will include authoritative SNMP engine id, security name, and authentication and privacy flags that you can then map with localy stored security parameters and fill in authentication and privacy secrets needed for packet decoding and verification. We'll get back to how to use this feature later.
First lets look at how to process authPriv authentication and privacy encrypted packets. This is done in a similar way to adding authentication, you will use a helper method authPriv to set security parameters related to authentication and privacy. Remeber, according to the SNMP standard, you cannot use privacy without authentication. Here is what needs to be changed in the original example to introduce privacy:
request.authPriv(ASCIIEncoding.UTF8.GetBytes("milan"), ASCIIEncoding.UTF8.GetBytes("myAuthSecret"), AuthenticationDigests.MD5, ASCIIEncoding.UTF8.GetBytes("myPrivSecret"), PrivacyProtocols.DES);
In this example, we are specifying authentication protocol as DES with the myPrivSecret secret (encryption password). When you call SnmpV3Packet.encode() method, packet with the above security configuration will have the ScopedPdu portion of the packet encrypted and then MD5 authenticated for security.
Decoding a response to an authPriv request requires security settings to be set correctly in the SnmpV3Packet class prior to decoding. Here is an example that will decode responses to the above configured requests:
// Create a response packet class SnmpV3Packet response = new SnmpV3Packet(); // Set authentication and privacy parameters to use with the incoming packet request.authPriv(ASCIIEncoding.UTF8.GetBytes("milan"), ASCIIEncoding.UTF8.GetBytes("myAuthSecret"), AuthenticationDigests.MD5, ASCIIEncoding.UTF8.GetBytes("myPrivSecret"), PrivacyProtocols.DES); // Decode response response.decode(inBuffer, inLength);
When preparing requests, you have to make sure that security name, authentication digest and secret and privacy protocol and secret match what is configured on the agent you wish to request information from.
Available AuthenticationDigests are MD5 and SHA1 and PrivacyProtocols are DES, TripleDES, AES-128, AES-192 and AES-256. Any combination of authentication digests and privacy protocols can be configured.
SNMP version 3 supports two types of notifications, traps and informs. Traps are identical to version 2 traps and are unacknowledged notifications sent by agents to managers. Informs are acknowledged notifications. Agent sends an inform notification and waits for acknowledgment. If acknowledgment is not received within the configured timeout period, inform notification is re-sent until a reply is received or maximum retry value has been reached.
Both trap and inform notifications support noAuthNoPriv, authNoPriv and authPriv security models but handle them in a different way.
Traps are generated by the agent and authoritative SNMP engine for a trap packet is the sending SNMP agent. Since generator of the message and authoritative engine are one and the same, there is no need for the SNMPv3 discovery process.
Inform are generated by the agent but authoritative SNMP engine is the manager (receiver). That means that sender of the inform notification has to perform SNMPv3 discovery to retrieve engine id (which some agents can be pre-configured with), boots and time values it requires to generate a valid inform notification packet.
To be able to work with informs, SNMP manager application needs to maintain local engine id, boots and time values that it can send to the agents when a SNMPv3 Discovery request is received. For processing of valid notifications, a list of security names (user names), authentication and privacy settings and related secrets need to be maintained for correct authentication and, if used, decryption of received notifications.
For trap handling, list of agent engine ids, security names, authentication and privacy settings and related secrets are needed.
It will be clearer what is needed in the examples below...
We'll begin with traps since they are easier to process. Trap uses agents (trap sender) engine id, boots and time values so all we need is to keep a list of configuration values that will allow us to process the packet.
Here is an example of agent settings entry:
public class USMPeer { protected OctetString _engineId; protected OctetString _securityName; protected AuthenticationDigests _authenticationType; protected PrivacyProtocols _privacyType; protected OctetString _authenticationSecret; protected OctetString _privacySecret; public USMPeer() { _engineId = new OctetString(); _securityName = new OctetString(); _authenticationType = AuthenticationDigests.None; _authenticationSecret = new OctetString(); _privacyType = PrivacyProtocols.None; _privacySecret = new OctetString(); } public USMPeer(OctetString engineId, OctetString securityName) : this() { _engineId.Set(engineId); _securityName.Set(securityName); } public USMPeer(OctetString engineId, OctetString securityName, AuthenticationDigests authDigest, OctetString authSecret) : this(engineId, securityName) { _authenticationType = authDigest; _authenticationSecret.Set(authSecret); } public USMPeer(OctetString engineId, OctetString securityName, AuthenticationDigests authDigest, OctetString authSecret, PrivacyProtocols privType, OctetString privSecret) : this(engineId, securityName, authDigest, authSecret) { _privacyType = privType; _privacySecret.Set(privSecret); } public bool IsMatch(OctetString engineId, OctetString securityname) { if (_engineId.Equals(engineId) && _securityName.Equals(securityname)) return true; return false; } public bool IsMatch(OctetString securityname) { if (_engineId.Length <= 0 && _securityName.Equals(securityname)) return true; return false; } public SnmpSharpNet.OctetString EngineId { get { return _engineId; } } public SnmpSharpNet.OctetString SecurityName { get { return _securityName; } } public SnmpSharpNet.AuthenticationDigests AuthenticationType { get { return _authenticationType; } set { _authenticationType = value; } } public SnmpSharpNet.PrivacyProtocols PrivacyType { get { return _privacyType; } set { _privacyType = value; } } public SnmpSharpNet.OctetString AuthenticationSecret { get { return _authenticationSecret; } } public SnmpSharpNet.OctetString PrivacySecret { get { return _privacySecret; } } }
When creating a peer information storage classes, it is a good idea to allow for a generic entry that doesn't depend on authoritative engine id but is selected based on SecurityName. This way you'll be able to have a group of devices all configured with the same security name, authentication and privacy parameters and not have to store and manage a separate entry for each one. I have done this by allowing EngineId to be length 0 meaning it doesn't matter what the engine id is.
Now that you have a class to store required values, create a collection to store them and populate it with agent values:
// Collection to hold agent information public List<USMPeer> _usmPeers = new List<USMPeer>(); // noAuthNoPriv agent with security name test _usmPeers.Add(new USMPeer(null, new OctetString("test"))); // authNoPriv agent using MD5 authentication with security name testMD5 and // authentication secret md5authNoPriv _usmPeers.Add(new USMPeer(null, new OctetString("testMD5"), AuthenticationDigests.MD5, new OctetString("md5authNoPriv"))); // authNoPriv agent using SHA-1 authentication with security name testSHA1 and // authentication secret sha1authNoPriv _usmPeers.Add(new USMPeer(null, new OctetString("testSHA1"), AuthenticationDigests.SHA1, new OctetString("sha1authNoPriv"))); // authPriv agent using MD5 authentication and DES privacy with security name testDESMD5, // authentication secret md5desauthPriv and privacy secret md5desAuthPriv _usmPeers.Add(new USMPeer(null, new OctetString("testDESMD5"), AuthenticationDigests.MD5, new OctetString("md5desauthPriv"), PrivacyProtocols.DES, new OctetString("md5desAuthPriv"))); // authPriv agent using SHA-1 authentication and DES privacy with security name testDESMD5, // authentication secret sha1desauthPriv and privacy secret sha1desauthPriv _usmPeers.Add(new USMPeer(null, new OctetString("testDESMD5"), AuthenticationDigests.SHA1, new OctetString("sha1desauthPriv"), PrivacyProtocols.DES, new OctetString("sha1desAuthPriv"))); // authPriv agent using MD5 authentication and AES-128 privacy with security name testAESMD5, // authentication secret md5aesAuthPriv and privacy secret md5aesAuthPriv _usmPeers.Add(new USMPeer(null, new OctetString("testAESMD5"), AuthenticationDigests.MD5, new OctetString("md5aesauthPriv"), PrivacyProtocols.AES128, new OctetString("md5aesAuthPriv"))); // authPriv agent using SHA-1 authentication and AES-128 privacy with security name testAESSHA1, // authentication secret sha1aesAuthPriv and privacy secret sha1aesAuthPriv _usmPeers.Add(new USMPeer(null, new OctetString("testAESSHA1"), AuthenticationDigests.SHA1, new OctetString("sha1aesauthPriv"), PrivacyProtocols.AES128, new OctetString("sha1aesAuthPriv")));
Above agent entries are not tied to a specific authoritative SNMP engine (or agent). To create an agent specific entry do the following:
// noAuthNoPriv agent with security name test tied to specific engine id _usmPeers.Add(new USMPeer( new OctetString(new byte[] { 0x04,0x0D,0x80,0x00,0x1F,0x88,0x80,0x9A,0x4A,0x00,0x00,0x2A,0xC0,0x9A,0x49}, new OctetString("test")));
You'll probably need a lookup routine to make it easy to find entries:
public USMPeer FindPeer(OctetString engineId, OctetString securityName) { // look for specific entires first trying to match both engineId and securityName if( engineId != null ) { foreach (USMPeer peer in _usmPeers) { if (peer.IsMatch(engineId, securityName)) return peer; } } // no specific entries found. Look for entries not assigned to specific engine id foreach (USMPeer peer in _usmPeers) { if (peer.IsMatch(securityName)) return peer; } return null; // match not found }
Now you can store and retrieve information you need about peers and can start working with traps.
There are no shortcuts in the library to handling the communications side of things. You will need to create a receiving socket, bind it and wait for packets. One way to do this is:
// We'll need a byte buffer to store incoming data byte[] inbuffer = new byte[32 * 1024]; // End point details of the host we received packet(s) from EndPoing peer = (EndPoint)new IPEndPoint(IPAddress.Any, 0); // Create a IP/UDP socket Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); // Bind the socket to the standard snmptrapd port = udp/162 socket.Bind((EndPoint)new IPEndPoint(IPAddress.Any, 162)); // Wait for a packet int inlen = _socket.ReceiveFrom(inbuffer, SocketFlags.None, ref peer); // Make sure we received some data instead of an empty packet. if( inlen <= 0 ) { Console.WriteLine("Received an invalid SNMP packet length 0 Bytes."); socket.Close(); return; }
To be able to fully process a packet, we need to get the authoritative engine id and security name from the packet we received. SnmpV3Packet.decode function is not suited for this purpose because it attempts to parse, authenticate and un-encrypt the packet which is not possible before we set the authentication and privacy parameters.
To get a peek at the information inside the packet without performing a full parse, you use SnmpV3Packet.GetUSM method:
// Make sure received packet is SNMP version 3 int ver = SnmpPacket.GetProtocolVersion(_inbuffer, inlen); if( ver != (int)SnmpVersion.Ver3 ) { Console.WriteLine("Received packet is not SNMP version 3."); socket.Close(); return; } SnmpV3Packet packet = new SnmpV3Packet(); // Do partial parse to get enough information to set authentication and privacy info UserSecurityModel usm = packet.GetUSM(_inbuffer, inlen); // Check this is a valid packet // Valid engine id has to be set in the packet. if( usm.EngineId.Length <= 0 ) { Console.WriteLine("Invalid packet. Authoritative engine id is not set."); _socket.Close(); return; } // Security name has to be set in a valid packet if( usm.SecurityName.Length <= 0 ) { Console.WriteLine("Invalid packet. Security name is not set."); _socket.Close(); return; }
With engine id and security name information we can lookup security parameters that apply to this packet using the above defined FindPeer() method and apply the found values to the SnmpV3Packet class to enable data validation and decoding:
// Locate the peer entry USMPeer peer = FindPeer(usm.EngineId, usm.SecurityName); if (peer == null) { // Couldn't find the peer so abort further processing... Console.WriteLine("SNMP packet from unknown peer."); socket.Close(); return; } // Set authentication type for the peer packet.USM.Authentication = peer.AuthenticationType; // Set privacy type from the peer packet.USM.Privacy = peer.PrivacyType; // If privacy is used, set the privacy secret in the packet class. if( peer.PrivacyType != PrivacyProtocols.None ) packet.USM.PrivacySecret.Set(peer.PrivacySecret); // If authentication is used, set the authentication secret in the packet class. if( peer.AuthenticationType != AuthenticationDigests.None ) packet.USM.AuthenticationSecret.Set(peer.AuthenticationSecret); // Decode the packet. packet.decode(_inbuffer, inlen);
This is a Trap packet demonstration so we want to make sure we received the correct packet type:
if (packet.Pdu.Type != PduType.V2Trap) { Console.WriteLine("Invalid SNMP version 3 packet type received."); }
Now that you've decoded the packet, what do you do with it?
Format of the V2TRAP packet is identical to any other SNMPv3 packet type. Specific requirements for the V2TRAP packets are that first OID in the VbList has to be trapSysUpTime.0 and second OID in the VbList has to be trapObjectID.0. When decoding the V2TRAP packet types, in both SNMP v2 and v3 classes, attempt is made to locate these two values and store them in the special variables, away from VbList, for easier access. To keep things simple, parser expects the two variables in question to be in the exactly prescribed positions. That means if trapSysUpTime.0 variable is not the first variable in VbList, it will not receive special treatment. Same is true for trapObjectID.0. If it is not the second variable in the VbList no special processing will take place.
Once parsed, you can use mandated trapSysUpTime.0 and trapObjectID.0 values:
Console.WriteLine("trapSysUpTime.0: {0}", packet.Pdu.TrapSysUpTime.ToString()); Console.WriteLine("trapObjectID.0 : {0}", packet.Pdu.TrapObjectID.ToString());
From here, access the VbList to get more information about the trap you received:
foreach( Vb v in packet.Pdu.VbList ) { Console.WriteLine("{0}: {1}", v.Oid.ToString(), v.Value.ToString()); }
That's all there is to trap reception and processing with SNMPv3.
Informs are a little more complex but not impossible :)
The two major differences between traps and informs in SNMP version 3 is that informs are acknowledged and that you (the manager application) are the authoritative SNMP engine.
What does this mean to you? It means you have to use a local engine id, keep track of the engine time and respond to discovery requests.
This sounds like a lot of work just to receive notifications, and I happen to agree, but that's the way it is and there are plenty of helper methods in the library to make this as easy as possible.
To begin, define you local engine parameters so you can deal with discovery requests:
// Define an engine id OctetString engineId = new OctetString(new byte[] { 0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00 }); // Store time value at the time your application started DateTime engineTime = DateTime.Now;
In this example, we'll stick with engine boots value of 0. I still haven't run into a peer that couldn't or wouldn't handle it and it is so much easier then keeping track of how many times the application was started and stopped.
When you receive a packet, you should check if it is a discovery request:
SnmpV3Packet packet = new SnmpV3Packet(); // Have a look inside the packet for security information UserSecurityModel usm = packet.GetUSM(inbuffer, inlen); // It's a discovery request if engine id or security name are empty values if (usm.EngineId.Length <= 0 || usm.SecurityName.Length <= 0) { // Calculate application uptime int dateTime = Convert.ToInt32(Math.Floor(DateTime.Now.Subtract(engineTime).TotalMinutes)); // Use SnmpV3Packet.DiscoveryResponse() method to create a discovery operation reply packet SnmpV3Packet discoveryResponse = SnmpV3Packet.DiscoveryResponse(packet.MessageId, 0, _engineId, 0, dateTime, 1); // BER encode the reply packet byte[] outPacket = discoveryResponse.encode(); // and send the encoded reply back to the originator of the request. socket.SendTo(outPacket, ep); }
If you looked closely in the code above, SnmpV3Packet.DiscoveryResponse() method is called with packet.MessageId. This was one of those coding accidents that, for once, worked out. We need the messageId value from the received packet but the way I designed the GetUSM() method, we only get the USM header back. Lucky, to get to the USM header inside the received SNMP packet, SnmpV3Packet class parses the packet up to that point, including the messageId value which is then stored in the class internal variable for us to retrieve.
While I am sure there are many ways to make this a lot better, prettier, etc. I'm not going to mess with it. It works the way it is.
So now what happens?, you are asking. Well, since we (or you) are in control, authentication and privacy for remote hosts is managed by SecurityName only. This is obviously the case since we are the authoritative engine id and all inform notifications arriving will have the same engine id.
Now you can use FindPeer() method (defined above) to find the peer security settings:
// We now received another packet after discovery process was completed... SnmpV3Packet packet = new SnmpV3Packet(); // Have a look inside the packet for security information UserSecurityModel usm = packet.GetUSM(inbuffer, inlen); // Find SNMP peer (remember, engineId value is your application sent value USMPeer peer = FindPeer(usm.EngineId, usm.SecurityName); if (peer == null) { // Couldn't find the peer so abort further processing... Console.WriteLine("SNMP packet from unknown peer."); socket.Close(); return; } // Set authentication type for the peer packet.USM.Authentication = peer.AuthenticationType; // Set privacy type from the peer packet.USM.Privacy = peer.PrivacyType; // If privacy is used, set the privacy secret in the packet class. if( peer.PrivacyType != PrivacyProtocols.None ) packet.USM.PrivacySecret.Set(peer.PrivacySecret); // If authentication is used, set the authentication secret in the packet class. if( peer.AuthenticationType != AuthenticationDigests.None ) packet.USM.AuthenticationSecret.Set(peer.AuthenticationSecret); // Decode the packet. packet.decode(_inbuffer, inlen);
We are not done yet. Inform notification sender expects an ack packet. This is no problem at all. Helper method SnmpV3Packet.BuildInformResponse() method will fill in all relevant information and format a new packet class appropriately to be sent as an ack for the received inform notification:
SnmpV3Packet infResponse = SnmpV3Packet.BuildInformResponse(packet); byte[] outPkt = infResponse.encode(); socket.SendTo(outPkt, ep);
Please note that maximum message size handling has not been implemented in Inform response messages. This will cause issues with agent handling of responses to very large inform notifications. Fix is planned in the next bug fix release.
SNMP version 3 requests can be made using 3 different security configurations. noAuthNoPriv is a request that doesn't use authentication or privacy, authNoPriv is an authenticated but not privacy protected (encrypted) request and authPriv an authenticated and privacy protected request and reply exchange.
This example will focus on how to make SNMP version 3 requests and process replies.
There are 2 steps required to make SNMP version 3 requests. First you need to "know" the authoritative engine id, boots and time values (this is the agent you are sending requests to) and second is making the actual request.
Getting the authoritative engine values is known as "discovery" process. To perform agent discovery, send an SNMP request with security name and engine id set to 0 length OctetString values and engine boots and time values set to 0.
Here is how you do this in SnmpSharpNet:
UdpTarget target = new UdpTarget((IPAddress)SomeHostIP); SecureAgentParameters param = new SecureAgentParameters(); if (!target.Discovery(param)) { Console.WriteLine("Discovery failed. Unable to continue..."); target.Close(); return; }
That's it. To summarize:
. What happens is that a request is sent to the agent, agent receives it and recognizes that values for a valid request are not present (engine id, boots and time values) and sends you back a REPORT (SNMPv3 error "message") with values you need in the packet. The call to UdpTarget.Discovery will receive the values in the REPORT returned by the agent, fill them into your SecureAgentParameters class and you are now ready to make requests.
Before you can make any requests, you need to set the Security Name (or user name) for the request. This is done through SecureAgentParameters.SecurityName property:
param.SecurityName.Set("mySecureName");
Security name can be a byte array or string value. Only ASCII values are correctly encoded.
To make a simplest, noAuthNoPriv request to an SNMP version 3 agent after discovery process has been completed, you can do the following:
// Construct a Protocol Data Unit (PDU) Pdu pdu = new Pdu(); // Set the request type (default is GET) pdu.Type = PduType.Get; // Add variables you wish to query pdu.VbList.Add("1.3.6.1.2.1.1.1.0"); // optional: make sure no authentication or privacy is configured in the // SecureAgentParameters class (see discovery section above) param.Authentication = AuthenticationDigests.None; param.Privacy = PrivacyProtocols.None; // optional: make sure you request REPORT packet return on errors param.Reportable = false; // Make a request. Request can throw a number of errors so wrap it in try/catch SnmpV3Packet result; try { SnmpV3Packet result = (SnmpV3Packet)target.Request(pdu, param); } catch( Exception ex ) { // deal with errors result = null; } if( result != null ) { // You have results to process here.... }
To make authenticated (authNoPriv) requests is not much harder. Follow the same logic as noAuthNoPriv except don't set the autentication digest value to AuthenticationDigests.None but set it to the digest you need to communicate with the agent and set the authentication secret (password).
// Set the authentication digest param.Authentication = AuthenticationDigests.MD5; // Set the authentication password param.AuthenticationSecret.Set("password");
You can use SHA-1 for authentication if that is what is configured on the agent:
// Set the authentication digest param.Authentication = AuthenticationDigests.SHA1; // Set the authentication password param.AuthenticationSecret.Set("password");
Make sure you set the authentication values (or privacy values in the authPriv requests) after discovery process has completed. If you set the authentication and privacy values in the SecureAgentParameters class and then make a call to UdpTarget.Discovery, you'll have to set those values again because Discovery call resets the SecureAgentParameters class to default values erasing all authentication and privacy settings.
Now to complete the circle, to make a authPriv call, you need to follow the above steps for discovery, request preparation and authentication configuration, plus configure privacy parameters. Here is what you need to do:
// Set the privacy protocol param.Privacy = PrivacyProtocols.DES; // Set the privacy password param.PrivacySecret.Set("CryptoPassword");
To use SHA-128 instead of DES:
// Set the privacy protocol param.Privacy = PrivacyProtocols.AES128; // Set the privacy password param.PrivacySecret.Set("CryptoPassword");
Done and done. Sending the request is done by calling UdpTarget.Request(param, pdu) and authentication and privacy protection of the outgoing request and received reply will be done for you.
Once you have made a successfull request, what do you do with the response?
Library has been designed to return SnmpPacket class instead of just data class like Pdu to allow you to examine all information returned by the agent response. SnmpV3Packet class inherits from SnmpPacket class so you can cast the return value to the version specific class you need.
First step is make sure you got the reply. If UdpTarget.Request returned a null, you've got yourself a strange and unexpected situation. Request was made but no valid reply received. At this point, you'll either have to make another request or abendon attempts to get data from that particular agent.
Assuming that values returned by UdpTarget.Request is not null, you should check if the contained Pdu type is not SnmpConstants.REPORT. If returned pdu type is Report, then agent detected an error and is sending you notification of it. Reports carry Oid/Value Vb pairs in the SnmpV3Packet.Pdu.VbList collection that you can examine to find our what the error is.
Once you check Pdu type is not Report, make sure there are no errors reported in the Pdu. If SnmpV3Packet.Pdu.ErrorCode != 0 you have an error in the request you made. Value SnmpV3Packet.Pdu.ErrorIndex will indicate the index of the value that caused the error in the VbList collection.
Moving on, if the Pdu type is not a Report and there are not errors, check that it is a SnmpConstants.RESPONE. If it is, you've got the return values to your request. You can retrieve them, enumerate them, print them or whatever else you'd like to do with them using SnmpV3Packet.Pdu.VbList class.
Here is an example:
try { SnmpV3Packet result = (SnmpV3Packet)target.Request(pdu, param); } catch( Exception ex ) { // deal with errors result = null; } if( result != null ) { if (result.Pdu.Type == PduType.Report) { // we've got an error Console.WriteLine("Report packet received."); string errstr = SNMPV3ReportError.TranslateError(pkt); Console.WriteLine("Error: {0}", errstr); target.Close(); return; } if (result.Pdu.ErrorStatus != 0) { Console.WriteLine("SNMP target has returned error code {0} on index {1}.", SnmpError.ErrorMessage(result.Pdu.ErrorStatus), result.Pdu.ErrorIndex); return; } // Process returned Oid/Variable pairs foreach( Vb v in result.Pdu.VbList ) { Console.WriteLine("{0}: {1} {2}", v.Key.ToString(), SnmpConstants.GetTypeName(v.Value.Type), v.Value.ToString()); } }
TrapAgent class is used to simplify sending of SNMP Trap notifications for all protocol versions by hiding the underlying Socket methods and packet creation process.
To dive into the code, this is how to send a simple Trap using SNMP version 1:
TrapAgent agent = new TrapAgent(); // Variable Binding collection to send with the trap VbCollection col = new VbCollection(); col.Add(new Oid("1.3.6.1.2.1.1.1.0"), new OctetString("Test string")); col.Add(new Oid("1.3.6.1.2.1.1.2.0"), new Oid("1.3.6.1.9.1.1.0")); col.Add(new Oid("1.3.6.1.2.1.1.3.0"), new TimeTicks(2324)); col.Add(new Oid("1.3.6.1.2.1.1.4.0"), new OctetString("Milan")); // Send the trap to the localhost port 162 agent.SendV1Trap(new IpAddress("localhost"), 162, "public", new Oid("1.3.6.1.2.1.1.1.0"), new IpAddress("127.0.0.1"), SnmpConstants.LinkUp, 0, 13432, col);
In VB.Net
Imports SnmpSharpNet Module Module1 Sub Main() Dim host As String = "localhost" Dim community As String = "public" Dim agent As TrapAgent = New TrapAgent() Dim col As VbCollection = New VbCollection() col.Add(New Oid("1.3.6.1.2.1.1.1.0"), New OctetString("Test string")) col.Add(New Oid("1.3.6.1.2.1.1.2.0"), New Oid("1.3.6.1.9.1.1.0")) Dim timeTickVal As UInt32 = 2324 col.Add(New Oid("1.3.6.1.2.1.1.3.0"), New TimeTicks(timeTickVal)) col.Add(New Oid("1.3.6.1.2.1.1.4.0"), New OctetString("MyName")) agent.SendV1Trap(New IpAddress("localhost"), 162, "public", _ New Oid("1.3.6.1.2.1.1.1.0"), New IpAddress("127.0.0.1"), _ SnmpConstants.LinkUp, 0, 13432, col) End Sub End Module
In the above example, we have send a SNMP version 1 LinkUp trap to the localhost port 162 with the community name public.
Parameters are as follows:
Sending a SNMP version 2 trap is very similar:
agent.SendV2Trap(new IpAddress("localhost"), 162, "public", 13433, new Oid(".1.3.6.1.6.3.1.1.5"), col);
Same in VB.Net
agent.SendV2Trap(New IpAddress("localhost"), 162, "public", 13433, _ New Oid(".1.3.6.1.6.3.1.1.5"), col)
V2Trap, specific Trap format for SNMP versions 2c and 3, is different from the format of SNMP Traps with SNMP version 1. For that reason, parameters are different. When sending SNMP version 2c notifications, you have to provide following parameters:
Sending SNMP version 3 has more options and more methods available to accommodate different security levels that are possible.
Simplest format SNMP version 3 Trap uses noAuthNoPriv (no authentication or privacy) security model. This is how you send a noAuthNoPriv Trap:
agent.SendV3Trap(new IpAddress("localhost"), 162, new byte[] { 0x00, 0x08, 0x02, 0x01, 0x20, 0x12, 0x14, 0xa0, 0xb1, 0xc2 }, 1, 500, "mysecurityname", 13434, new Oid(".1.3.6.1.6.3.1.1.5"), col);
Same in VB.Net
agent.SendV3Trap(New IpAddress("localhost"), 162, _ New Byte() {&H0, &H8, &H2, &H1, &H20, &H12, &H14, &HA0, &HB1, &HC2}, _ 1, 500, "mysecurityname", 13434, New Oid(".1.3.6.1.6.3.1.1.5"), col)
Parameters for SNMP version 3 traps is different from previous versions of the protocol because of the security model implementation that needs to be present event when privacy and authentication is not used. To successfuly send a SNMPv3 Trap you will need to supply following values:
Above example is going to send a trap without authentication or privacy available in SNMP version 3. To add authentication for a authNoPriv (authentication without privacy encryption) security model, you can call a method that adds 2 parameters to the end of the parameter list, AuthenticationDigest, which digest to use to authenticate the trap, and a byte array holding authentication secret (password):
agent.SendV3Trap(new IpAddress("localhost"), 162, new byte[] { 0x00, 0x08, 0x02, 0x01, 0x20, 0x12, 0x14, 0xa0, 0xb1, 0xc2 }, 1, 501, "myauthenticationname", 13435, new Oid(".1.3.6.1.6.3.1.1.5"), col, AuthenticationDigests.MD5,ASCIIEncoding.UTF8.GetBytes("authpasswd"));
In VB.Net
agent.SendV3Trap(New IpAddress("localhost"), 162, _ New Byte() {&H0, &H8, &H2, &H1, &H20, &H12, &H14, &HA0, &HB1, &HC2}, _ 1, 501, "myauthenticationname", 13435, New Oid(".1.3.6.1.6.3.1.1.5"), col, _ AuthenticationDigests.MD5, _ System.Text.ASCIIEncoding.UTF8.GetBytes("authpasswd"))
As you can see, noAuthNoPriv and authPriv SNMP version 3 Trap calls are identical except for the two additional parameters when adding authentication to the notifications. Similar approach is made when adding privacy to the authNoPriv notification. There are two additional parameters to specify PrivacyProtocol used to encrypt the information inside the SNMP version 3 trap and a byte array holding the privacy secret (password used to encrypt data):
agent.SendV3Trap(new IpAddress("localhost"), 162, new byte[] { 0x00, 0x08, 0x02, 0x01, 0x20, 0x12, 0x14, 0xa0, 0xb1, 0xc2 }, 1, 501, "myauthenticationname", 13435, new Oid(".1.3.6.1.6.3.1.1.5"), col, AuthenticationDigests.MD5, ASCIIEncoding.UTF8.GetBytes("authpasswd"), PrivacyProtocols.DES, ASCIIEncoding.UTF8.GetBytes("privpasswd"));
In VB.Net
agent.SendV3Trap(New IpAddress("localhost"), 162, _ New Byte() {&H0, &H8, &H2, &H1, &H20, &H12, &H14, &HA0, &HB1, &HC2}, _ 1, 501, "myauthenticationname", 13435, _ New Oid(".1.3.6.1.6.3.1.1.5"), col, _ AuthenticationDigests.MD5, _ System.Text.ASCIIEncoding.UTF8.GetBytes("authpasswd"), _ PrivacyProtocols.DES, _ System.Text.ASCIIEncoding.UTF8.GetBytes("privpasswd"))
This Trap will be sent both authenticated and privacy encrypted. You can changed authentication digest to SHA1 or privacy encryption to AES128 if that is what your implementation requires but base operation remains the same.
One important thing to remember is that noAuthPriv security model, without authentication and with privacy encryption, is not supported by the SNMP version 3.
For more details see library documentation.
When sending SNMPv3 traps, you are in control. Sender of V2TRAP notifications using protocol version 3 is the authoritative SNMP engine, meaning it supplies the engine id, engine boots and engine time values.
Since you are both the originator of the notification and authoritative SNMP engine, there is nothing you need to "discover" making SNMP version 3 Trap notifications the only SNMP operation that doesn't use the discovery procedure.
Since it is that straight forward, we can dive into the code straight away.
Here is a simplest possible example of how to build a noAuthNoPriv SNMP notification:
SnmpV3Packet packet = new SnmpV3Packet(); // Set the security name packet.NoAuthNoPriv(ASCIIEncoding.UTF8.GetBytes("mysecurityname")); // Set your engine id packet.USM.EngineId.Set(new byte[] { 0x00, 0x08, 0x02, 0x01, 0x10, 0x12, 0x14, 0xa0, 0xb1, 0xc2 }); // Engine id is also stored in the ScopedPdu so just duplicate it packet.ScopedPdu.ContextEngineId.Set(packet.USM.EngineId); // Set your engine boots (can be 0) packet.USM.EngineBoots = 20; // Set your engine time packet.USM.EngineTime = 200; // Set message reportable flag to false. You don't really want to receive errors packet.MsgFlags.Reportable = false; // Pdu type is V2TRAP packet.Pdu.Type = PduType.V2Trap; // Set the TRAP object ID value packet.Pdu.TrapObjectID.Set(new int[] { 1, 3, 6, 1, 2, 1, 2, 2, 1, 0 }); // Set your system up time value (this has nothing to do with engineTime) packet.Pdu.TrapSysUpTime.Value = 23456; // Add variable bindings to the Pdu to further describe the TRAP packet.Pdu.VbList.Add(new Oid(new int[] { 1, 3, 6, 1, 2, 1, 1, 1, 0 }), new OctetString("Test noAuthNoPriv")); // Finally, encode into a byte buffer ready for sending byte[] outBuffer = packet.encode(); // Send it to the manager sock.SendTo(outBuffer,new IPEndPoint(IPAddress.Loopback, 162));
In this example I just made up the authoritative SNMP engine values (id, boots and time). While this will work, it is pointless to use SNMPv3 and then have a massive security hole like this in your implementation. What you should do is generate an engine id that is unique, keep track how many times your snmp engine has been started to use as engineBoots value and keep track of how long the engine has been running for the engineTime value.
Sending the above example to a net-snmp snmptrapd application you will get the following debug dump:
dumpx_recv: 02 01 03 dumpv_recv: Integer: 3 (0x03) dumph_recv: SNMPv3 Message dumph_recv: SNMP Version Number dumpx_recv: 02 01 03 dumpv_recv: Integer: 3 (0x03) dumph_recv: msgGlobalData dumph_recv: msgID dumpx_recv: 02 04 7B EA 66 C6 dumpv_recv: Integer: 2078959302 (0x7BEA66C6) dumph_recv: msgMaxSize dumpx_recv: 02 03 01 00 00 dumpv_recv: Integer: 65536 (0x10000) dumph_recv: msgFlags dumpx_recv: 04 01 00 dumpv_recv: String: . dumph_recv: msgSecurityModel dumpx_recv: 02 01 03 dumpv_recv: Integer: 3 (0x03) dumph_recv: SM msgSecurityParameters dumph_recv: msgAuthoritativeEngineID dumpx_recv: 04 0A 00 08 02 01 10 12 14 A0 B1 C2 dumpv_recv: String: ....... ±A dumph_recv: msgAuthoritativeEngineBoots dumpx_recv: 02 01 14 dumpv_recv: Integer: 20 (0x14) dumph_recv: msgAuthoritativeEngineTime dumpx_recv: 02 02 00 C8 dumpv_recv: Integer: 200 (0xC8) dumph_recv: msgUserName dumpx_recv: 04 09 6D 69 6C 61 6E 73 68 61 37 dumpv_recv: String: milansha7 dumph_recv: msgAuthenticationParameters dumpx_recv: 04 00 dumpv_recv: String: dumph_recv: msgPrivacyParameters dumpx_recv: 04 00 dumpv_recv: String: dumph_recv: ScopedPDU dumph_recv: contextEngineID dumpx_recv: 04 0A 00 08 02 01 10 12 14 A0 B1 C2 dumpv_recv: String: ....... ±A dumph_recv: contextName dumpx_recv: 04 00 dumpv_recv: String: dumph_recv: TRAP2 dumpv_recv: Command TRAP2 dumph_recv: request_id dumpx_recv: 02 04 62 EE 96 81 dumpv_recv: Integer: 1659803265 (0x62EE9681) dumph_recv: error status dumpx_recv: 02 01 00 dumpv_recv: Integer: 0 (0x00) dumph_recv: error index dumpx_recv: 02 01 00 dumpv_recv: Integer: 0 (0x00) dumph_recv: VarBindList dumph_recv: VarBind dumph_recv: Name dumpx_recv: 06 08 2B 06 01 02 01 01 03 00 dumpv_recv: ObjID: DISMAN-EVENT-MIB::sysUpTimeInstance dumph_recv: Value dumpx_recv: 43 02 5B A0 dumpv_recv: UInteger: 23456 (0x5BA0) dumph_recv: VarBind dumph_recv: Name dumpx_recv: 06 0A 2B 06 01 06 03 01 01 04 01 00 dumpv_recv: ObjID: SNMPv2-MIB::snmpTrapOID.0 dumph_recv: Value dumpx_recv: 06 09 2B 06 01 02 01 02 02 01 00 dumpv_recv: ObjID: IF-MIB::ifEntry.0 dumph_recv: VarBind dumph_recv: Name dumpx_recv: 06 08 2B 06 01 02 01 01 01 00 dumpv_recv: ObjID: SNMPv2-MIB::sysDescr.0 dumph_recv: Value dumpx_recv: 04 11 54 65 73 74 20 6E 6F 41 75 74 68 4E 6F 50 72 69 76 dumpv_recv: String: Test noAuthNoPriv
To configure a noAuthNoPriv user, add createUser -e 0x00080201101214a0b1c2 mysecurityname to the snmptrapd.conf configuration file.
As you can see from the above debug dump, packet is received and decoded correctly.
Using SNMPv3 without authentication is not very useful, here is now to introduce authentication into your TRAP sending app:
SnmpV3Packet packet = new SnmpV3Packet(); // Set security name and authentication parameters needed for a authNoPriv TRAP packet.authNoPriv(ASCIIEncoding.UTF8.GetBytes("myauthenticationname"), ASCIIEncoding.UTF8.GetBytes("authpasswd"), AuthenticationDigests.MD5); // Set your engine id packet.USM.EngineId.Set(new byte[] { 0x00, 0x08, 0x02, 0x01, 0x10, 0x12, 0x14, 0xa0, 0xb1, 0xc2 }); // Engine id is also stored in the ScopedPdu so just duplicate it packet.ScopedPdu.ContextEngineId.Set(packet.USM.EngineId); // Set your engine boots (can be 0) packet.USM.EngineBoots = 20; // Set your engine time packet.USM.EngineTime = 200; // Set message reportable flag to false. You don't really want to receive errors packet.MsgFlags.Reportable = false; // Pdu type is V2TRAP packet.Pdu.Type = PduType.V2Trap; // Set the TRAP object ID value packet.Pdu.TrapObjectID.Set(new int[] { 1, 3, 6, 1, 2, 1, 2, 2, 1, 0 }); // Set your system up time value (this has nothing to do with engineTime) packet.Pdu.TrapSysUpTime.Value = 23456; // Add variable bindings to the Pdu to further describe the TRAP packet.Pdu.VbList.Add(new Oid(new int[] { 1, 3, 6, 1, 2, 1, 1, 1, 0 }), new OctetString("Test noAuthNoPriv")); // Finally, encode into a byte buffer ready for sending byte[] outBuffer = packet.encode(); // Send it to the manager sock.SendTo(outBuffer,new IPEndPoint(IPAddress.Loopback, 162));
Packet dump on the receiving side shows that authentication is correctly encoded and verified:
dumpx_recv:02 01 03 dumpv_recv: Integer: 3 (0x03) dumph_recv: SNMPv3 Message dumph_recv: SNMP Version Number dumpx_recv: 02 01 03 dumpv_recv: Integer: 3 (0x03) dumph_recv: msgGlobalData dumph_recv: msgID dumpx_recv: 02 04 7C 5C 95 AF dumpv_recv: Integer: 2086442415 (0x7C5C95AF) dumph_recv: msgMaxSize dumpx_recv: 02 03 01 00 00 dumpv_recv: Integer: 65536 (0x10000) dumph_recv: msgFlags dumpx_recv: 04 01 01 dumpv_recv: String: . dumph_recv: msgSecurityModel dumpx_recv: 02 01 03 dumpv_recv: Integer: 3 (0x03) dumph_recv: SM msgSecurityParameters dumph_recv: msgAuthoritativeEngineID dumpx_recv: 04 0A 00 08 02 01 10 12 14 A0 B1 C2 dumpv_recv: String: ....... ±A dumph_recv: msgAuthoritativeEngineBoots dumpx_recv: 02 01 14 dumpv_recv: Integer: 20 (0x14) dumph_recv: msgAuthoritativeEngineTime dumpx_recv: 02 02 00 C8 dumpv_recv: Integer: 200 (0xC8) dumph_recv: msgUserName dumpx_recv: 04 14 6D 79 61 75 74 68 65 6E 74 69 63 61 74 69 6F 6E 6E 61 6D 65 dumpv_recv: String: myauthenticationname dumph_recv: msgAuthenticationParameters dumpx_recv: 04 0C 8F DD 59 BF F8 A1 2A C2 5E 68 FC 71 dumpv_recv: String: .YY¿o¡*A^hüq dumph_recv: msgPrivacyParameters dumpx_recv: 04 00 dumpv_recv: String: dumph_recv: ScopedPDU dumph_recv: contextEngineID dumpx_recv: 04 0A 00 08 02 01 10 12 14 A0 B1 C2 dumpv_recv: String: ....... ±A dumph_recv: contextName dumpx_recv: 04 00 dumpv_recv: String: dumph_recv: TRAP2 dumpv_recv: Command TRAP2 dumph_recv: request_id dumpx_recv: 02 04 11 4E 18 23 dumpv_recv: Integer: 290330659 (0x114E1823) dumph_recv: error status dumpx_recv: 02 01 00 dumpv_recv: Integer: 0 (0x00) dumph_recv: error index dumpx_recv: 02 01 00 dumpv_recv: Integer: 0 (0x00) dumph_recv: VarBindList dumph_recv: VarBind dumph_recv: Name dumpx_recv: 06 08 2B 06 01 02 01 01 03 00 dumpv_recv: ObjID: DISMAN-EVENT-MIB::sysUpTimeInstance dumph_recv: Value dumpx_recv: 43 02 5B A0 dumpv_recv: UInteger: 23456 (0x5BA0) dumph_recv: VarBind dumph_recv: Name dumpx_recv: 06 0A 2B 06 01 06 03 01 01 04 01 00 dumpv_recv: ObjID: SNMPv2-MIB::snmpTrapOID.0 dumph_recv: Value dumpx_recv: 06 09 2B 06 01 02 01 02 02 01 00 dumpv_recv: ObjID: IF-MIB::ifEntry.0 dumph_recv: VarBind dumph_recv: Name dumpx_recv: 06 08 2B 06 01 02 01 01 01 00 dumpv_recv: ObjID: SNMPv2-MIB::sysDescr.0 dumph_recv: Value dumpx_recv: 04 0F 54 65 73 74 20 61 75 74 68 4E 6F 50 72 69 76 dumpv_recv: String: Test authNoPriv
For the full benefit of SNMPv3, you will use authPriv security model that includes both authentication and privacy encryption of the Pdu. To enable encryption, you need to initialize the SNMP packet with the authPriv() method:
SnmpV3Packet packet = new SnmpV3Packet(); // Set security name and authentication and privacy parameters needed for authPriv TRAP packet.authPriv(ASCIIEncoding.UTF8.GetBytes("myauthprivname"), ASCIIEncoding.UTF8.GetBytes("authpasswd"), AuthenticationDigests.MD5, ASCIIEncoding.UTF8.GetBytes("privpasswd"), PrivacyProtocols.DES); // Set your engine id packet.USM.EngineId.Set(new byte[] { 0x00, 0x08, 0x02, 0x01, 0x10, 0x12, 0x14, 0xa0, 0xb1, 0xc2 }); // Engine id is also stored in the ScopedPdu so just duplicate it packet.ScopedPdu.ContextEngineId.Set(packet.USM.EngineId); // Set your engine boots (can be 0) packet.USM.EngineBoots = 20; // Set your engine time packet.USM.EngineTime = 200; // Set message reportable flag to false. You don't really want to receive errors packet.MsgFlags.Reportable = false; // Pdu type is V2TRAP packet.Pdu.Type = PduType.V2Trap; // Set the TRAP object ID value packet.Pdu.TrapObjectID.Set(new int[] { 1, 3, 6, 1, 2, 1, 2, 2, 1, 0 }); // Set your system up time value (this has nothing to do with engineTime) packet.Pdu.TrapSysUpTime.Value = 23456; // Add variable bindings to the Pdu to further describe the TRAP packet.Pdu.VbList.Add(new Oid(new int[] { 1, 3, 6, 1, 2, 1, 1, 1, 0 }), new OctetString("Test authNoPriv")); // Finally, encode into a byte buffer ready for sending byte[] outBuffer = packet.encode(); // Send it to the manager sock.SendTo(outBuffer,new IPEndPoint(IPAddress.Loopback, 162));
Receiver debug output will look like this:
dumpx_recv:02 01 03 dumpv_recv: Integer: 3 (0x03) dumph_recv: SNMPv3 Message dumph_recv: SNMP Version Number dumpx_recv: 02 01 03 dumpv_recv: Integer: 3 (0x03) dumph_recv: msgGlobalData dumph_recv: msgID dumpx_recv: 02 04 5A E6 5D 23 dumpv_recv: Integer: 1525046563 (0x5AE65D23) dumph_recv: msgMaxSize dumpx_recv: 02 03 01 00 00 dumpv_recv: Integer: 65536 (0x10000) dumph_recv: msgFlags dumpx_recv: 04 01 03 dumpv_recv: String: . dumph_recv: msgSecurityModel dumpx_recv: 02 01 03 dumpv_recv: Integer: 3 (0x03) dumph_recv: SM msgSecurityParameters dumph_recv: msgAuthoritativeEngineID dumpx_recv: 04 0A 00 08 02 01 10 12 14 A0 B1 C2 dumpv_recv: String: ....... ±A dumph_recv: msgAuthoritativeEngineBoots dumpx_recv: 02 01 14 dumpv_recv: Integer: 20 (0x14) dumph_recv: msgAuthoritativeEngineTime dumpx_recv: 02 02 00 C8 dumpv_recv: Integer: 200 (0xC8) dumph_recv: msgUserName dumpx_recv: 04 0E 6D 79 61 75 74 68 70 72 69 76 6E 61 6D 65 dumpv_recv: String: myauthprivname dumph_recv: msgAuthenticationParameters dumpx_recv: 04 0C 13 7F 7F 26 00 C2 51 F3 16 AB 25 04 dumpv_recv: String: ...&.AQó.«%. dumph_recv: msgPrivacyParameters dumpx_recv: 04 08 00 00 00 14 45 F4 DA B1 dumpv_recv: String: ....EôU± dumph_recv: ScopedPDU dumph_recv: contextEngineID dumpx_recv: 04 0A 00 08 02 01 10 12 14 A0 B1 C2 dumpv_recv: String: ....... ±A dumph_recv: contextName dumpx_recv: 04 00 dumpv_recv: String: dumph_recv: TRAP2 dumpv_recv: Command TRAP2 dumph_recv: request_id dumpx_recv: 02 04 7F 0B B7 B2 dumpv_recv: Integer: 2131474354 (0x7F0BB7B2) dumph_recv: error status dumpx_recv: 02 01 00 dumpv_recv: Integer: 0 (0x00) dumph_recv: error index dumpx_recv: 02 01 00 dumpv_recv: Integer: 0 (0x00) dumph_recv: VarBindList dumph_recv: VarBind dumph_recv: Name dumpx_recv: 06 08 2B 06 01 02 01 01 03 00 dumpv_recv: ObjID: DISMAN-EVENT-MIB::sysUpTimeInstance dumph_recv: Value dumpx_recv: 43 02 5B A0 dumpv_recv: UInteger: 23456 (0x5BA0) dumph_recv: VarBind dumph_recv: Name dumpx_recv: 06 0A 2B 06 01 06 03 01 01 04 01 00 dumpv_recv: ObjID: SNMPv2-MIB::snmpTrapOID.0 dumph_recv: Value dumpx_recv: 06 09 2B 06 01 02 01 02 02 01 00 dumpv_recv: ObjID: IF-MIB::ifEntry.0 dumph_recv: VarBind dumph_recv: Name dumpx_recv: 06 08 2B 06 01 02 01 01 01 00 dumpv_recv: ObjID: SNMPv2-MIB::sysDescr.0 dumph_recv: Value dumpx_recv: 04 0D 54 65 73 74 20 61 75 74 68 50 72 69 76 dumpv_recv: String: Test authPriv
As you can see from the dump, both authentication and privacy parameters are present in the packet and data is correctly received, authenticated and unencrypted by the receiving manager.
To generate SNMP receiver dump data I have used net-snmp snmptrapd.exe utility version 5.4.2.1 on Windows with the following snmptrapd.conf configuration:
createUser -e 0x00080201101214a0b1c2 mysecurityname createUser -e 0x00080201101214a0b1c2 myauthenticationname MD5 authpasswd createUser -e 0x00080201101214a0b1c2 myauthprivname MD5 authpasswd DES privpasswd
Test output was collected by executing snmptrapd -Le -Ddump.
Apparently not everybody is as interested in the background workings of the SNMP protocol as I am. For this reason I have created a very simple to use utility class that will allow you to make requests and collect replies without worrying too much about how it's all done.
For that reason I have created SimpleSnmp class. It makes common SNMP version 1 and 2c operations simple. Version 3 is not included because there is a security aspect that, if simplified too much, will no longer be secure.
To dive straight in, here is a SNMP-Get request example:
string host = "localhost"; string community = "public"; SimpleSnmp snmp = new SimpleSnmp(host, community); if (!snmp.Valid) { Console.WriteLine("SNMP agent host name/ip address is invalid."); return; } Dictionary<Oid,AsnType> result = snmp.Get(SnmpVersion.Ver1, new string[] { ".1.3.6.1.2.1.1.1.0"} ); if (result == null) { Console.WriteLine("No results received."); return; } foreach (KeyValuePair kvp in result) { Console.WriteLine("{0}: {1} {2}", kvp.Key.ToString(), SnmpConstants.GetTypeName(kvp.Value.Type), kvp.Value.ToString()); }
On my laptop, result looks like this:
1.3.6.1.2.1.1.1.0: OctetString "Dual core Intel notebook"
Obviously, you can request multiple values in a single request just by adding them to the SimpleSnmp.Get() OID string array.
Methods are also available for GetNext:
Dictionary<Oid,AsnType> result = snmp.GetNext(SnmpVersion.Ver2, new string[] { ".1.3.6.1.2.1.1.1", ".1.3.6.1.2.1.1.2" } );
GetBulk:
Dictionary<Oid,AsnType> result = snmp.GetBulk(new string[] { ".1.3.6.1.2", ".1.3.6.1.3"} );
Set:
Dictionary<Oid,AsnType> result = snmp.Set(SnmpVersion.Ver2, new Vb[] { new Vb(new Oid(".1.3.6.1.2.1.1.1.0"), new OctetString("New sysDescr.0") } );
and Walk:
Dictionary result = snmp.Walk(SnmpVersion.Ver2, ".1.3.6.1.2.1.1.1");
SimpleSnmp.Walk uses GetBulk with SNMP version 2c. If SNMP version 1 is selected, GetNext operation is used and is considerably slower.
To control how GetBulk and SNMP version 2 walk performs, you can set SimpleSnmp.NonRepeaters and SimpleSnmp.MaxRepetitions values to adjust how GetBulk calls are made.
Prior to using the class, you can check the status of the SimpleSnmp.Valid property to verify class is in correctly initialized. This property will validate that agent name was correctly resolved to an IP address and that community name is set.
Performing simple SNMP operations using SimpleSnmp methods is quick and easy with VB.Net. Here is a collection of quick examples that shows how to use it.
SNMP Get operation
Imports System Imports SnmpSharpNet Module Module1 Sub Main() Dim host As String = "localhost" Dim community As String = "public" Dim requestOid() As String Dim result As Dictionary(Of Oid, AsnType) requestOid = New String() {"1.3.6.1.2.1.1.1.0", "1.3.6.1.2.1.1.2.0"} Dim snmp As SimpleSnmp = New SimpleSnmp(host, community) If Not snmp.Valid Then Console.WriteLine("Invalid hostname/community.") Exit Sub End If result = snmp.Get(SnmpVersion.Ver1, requestOid) If result IsNot Nothing Then Dim kvp As KeyValuePair(Of Oid, AsnType) For Each kvp In result Console.WriteLine("{0}: ({1}) {2}", kvp.Key.ToString(), _ SnmpConstants.GetTypeName(kvp.Value.Type), _ kvp.Value.ToString()) Next Else Console.WriteLine("No results received.") End If End Sub End Module
SNMP GetNext operation
Performing GetNext operations is just as easy. With this example you can "walk" MIB entries that start with Oid 1.3.6.1.2.1.1:
Imports System Imports SnmpSharpNet Module Module1 Sub Main() Dim host As String = "localhost" Dim community As String = "public" Dim requestOid() As String Dim result As Dictionary(Of Oid, AsnType) Dim rootOid As Oid = New Oid("1.3.6.1.2.1.1") Dim nextOid As Oid = rootOid Dim keepGoing As Boolean = True requestOid = New String() {rootOid.ToString()} Dim snmp As SimpleSnmp = New SimpleSnmp(host, community) If Not snmp.Valid Then Console.WriteLine("Invalid hostname/community.") Exit Sub End If While keepGoing result = snmp.GetNext(SnmpVersion.Ver1, New String() {nextOid.ToString()}) If result IsNot Nothing Then Dim kvp As KeyValuePair(Of Oid, AsnType) For Each kvp In result If rootOid.IsRootOf(kvp.Key) Then Console.WriteLine("{0}: ({1}) {2}", kvp.Key.ToString(), _ SnmpConstants.GetTypeName(kvp.Value.Type), _ kvp.Value.ToString()) nextOid = kvp.Key Else keepGoing = False End If Next Else Console.WriteLine("No results received.") keepGoing = False End If End While End Sub End Module
SNMP GetBulk operation
GetBulk is a SNMP v2 request type that operates similarly to the GetNext request except that it returns MaxRepetitions number of GetNext operation resuts as part of a single request.
Here is an example of how to perform a GetBulk query:
Imports System Imports SnmpSharpNet Module Module1 Sub Main() ' SNMP Agent hostname or IP address Dim host As String = "localhost" ' SNMP community name Dim community As String = "public" ' Dictionary to store values returned by GetBulk calls Dim result As Dictionary(Of Oid, AsnType) ' Root Oid for the request (ifTable.ifEntry.ifDescr in this example) Dim rootOid As Oid = New Oid("1.3.6.1.2.1.2.2.1.2") Dim nextOid As Oid = rootOid Dim keepGoing As Boolean = True ' Initialize SimpleSnmp class Dim snmp As SimpleSnmp = New SimpleSnmp(host, community) If Not snmp.Valid Then Console.WriteLine("Invalid hostname/community.") Exit Sub End If ' Set NonRepeaters and MaxRepetitions for the GetBulk request (optional) snmp.NonRepeaters = 0 snmp.MaxRepetitions = 20 While keepGoing ' Make a request result = snmp.GetBulk(New String() {nextOid.ToString()}) ' Check SNMP agent returned valid results If result IsNot Nothing Then Dim kvp As KeyValuePair(Of Oid, AsnType) ' Loop through returned values For Each kvp In result ' Check that returned Oid is part of the original root Oid If rootOid.IsRootOf(kvp.Key) Then Console.WriteLine("{0}: ({1}) {2}", kvp.Key.ToString(), _ SnmpConstants.GetTypeName(kvp.Value.Type), _ kvp.Value.ToString()) ' Store last valid Oid to use in additional GetBulk requests (if required) nextOid = kvp.Key Else ' We found a value outside of the root Oid tree. Do not perform additional GetBulk ops keepGoing = False End If Next Else Console.WriteLine("No results received.") keepGoing = False End If End While End Sub End Module
SNMP Walk operation
Walk is not really an SNMP operation but a convenience method that performs multiple GetNext (for SNMP v1) or GetBulk (for SNMP v2) requests and returns all Oid/Value pairs under the requested root Oid.
This example will perform the same function as the above GetNext example:
Imports System Imports SnmpSharpNet Module Module1 Sub Main() Dim host As String = "localhost" Dim community As String = "public" Dim result As Dictionary(Of Oid, AsnType) Dim snmp As SimpleSnmp = New SimpleSnmp(host, community) If Not snmp.Valid Then Console.WriteLine("Invalid hostname/community.") Exit Sub End If result = snmp.Walk(SnmpVersion.Ver1, "1.3.6.1.2.1.1") If result IsNot Nothing Then Dim kvp As KeyValuePair(Of Oid, AsnType) For Each kvp In result Console.WriteLine("{0}: ({1}) {2}", kvp.Key.ToString(), _ SnmpConstants.GetTypeName(kvp.Value.Type), _ kvp.Value.ToString()) Next Else Console.WriteLine("No results received.") End If End Sub End Module
Above example will perform as many GetNext operations in the background as necessary to retrieve all values that are part of the requested MIB branch. If you change the protocol version number in the GetBulk call from SnmpVersion.Ver1 to SnmpVersion.Ver2, Walk function will use GetBulk requests instead of GetNext and be much more efficient.
SNMP Set operation
Set operation allows you to change values associated with individual Object Identifiers. To be able to perform Set operations, target Oid has to be available for writing (you'll need to check MIB files for the specific device to check if it is) and you will need to know the read/write SNMP community name for the device.
Here is a simple example that shows how to change contact information for a device using SNMP Set method:
Imports System Imports SnmpSharpNet Module Module1 Sub Main() ' SNMP Agent hostname or IP address Dim host As String = "localhost" ' SNMP community name - has to be read/write access for Set operation Dim community As String = "private" ' Dictionary to store values returned by GetBulk calls Dim result As Dictionary(Of Oid, AsnType) ' This is the Oid of the value we will change with this example Dim sysContactOid As Oid = New Oid("1.3.6.1.2.1.1.4.0") ' The new value for the above Oid Dim sysContactNewValue As OctetString = New OctetString("Support department - 555-333-4444") ' Initialize SimpleSnmp class Dim snmp As SimpleSnmp = New SimpleSnmp(host, community) If Not snmp.Valid Then Console.WriteLine("Invalid hostname/community.") Exit Sub End If ' Create an array of Variable Bindings (Vb class) of Oid/Values you with to set Dim vbCollection() As Vb ' Variable Binding contains an Oid and value associated with that Oid Dim vb As Vb = New Vb(sysContactOid, sysContactNewValue) ' Add one or more Vb classes to the collection vbCollection = New Vb() {vb} ' Make set request result = snmp.Set(SnmpVersion.Ver1, vbCollection) ' Check SNMP agent returned valid results If result IsNot Nothing Then Dim kvp As KeyValuePair(Of Oid, AsnType) ' Loop through returned values For Each kvp In result Console.WriteLine("{0}: ({1}) {2}", kvp.Key.ToString(), _ SnmpConstants.GetTypeName(kvp.Value.Type), _ kvp.Value.ToString()) Next Else Console.WriteLine("Set operation failed.") End If End Sub End Module
"Walk" is not a strictly speaking an SNMP operation. When you talk about "walking the MIB" you are talking about sequentially retrieving values from the MIB. To sequentially retrieve MIB values, you can use:
"Walk" is not a strictly speaking an SNMP operation. When you talk about "walking the MIB" you are talking about sequentially retrieving values from the MIB. To sequentially retrieve MIB values, you can use:
takes an OID and returns the OID following the requested MIB with the value associated with it
also takes an OID and then returns MaxRepetitions number of MIB entries following the requested OID
If you have any choice at all, you should always use GET-BULK. It results in fewer requests and much better retrieval speed.
using System; using System.Net; using SnmpSharpNet; namespace sharpwalk { class Program { static void Main(string[] args) { // SNMP community name OctetString community = new OctetString("public"); // Define agent parameters class AgentParameters param = new AgentParameters(community); // Set SNMP version to 1 param.Version = SnmpVersion.Ver1; // Construct the agent address object // IpAddress class is easy to use here because // it will try to resolve constructor parameter if it doesn't // parse to an IP address IpAddress agent = new IpAddress("127.0.0.1"); // Construct target UdpTarget target = new UdpTarget((IPAddress)agent, 161, 2000, 1); // Define Oid that is the root of the MIB // tree you wish to retrieve Oid rootOid = new Oid("1.3.6.1.2.1.2.2.1.2"); // ifDescr // This Oid represents last Oid returned by // the SNMP agent Oid lastOid = (Oid)rootOid.Clone(); // Pdu class used for all requests Pdu pdu = new Pdu(PduType.GetNext); // Loop through results while (lastOid != null) { // When Pdu class is first constructed, RequestId is set to a random value // that needs to be incremented on subsequent requests made using the // same instance of the Pdu class. if (pdu.RequestId != 0) { pdu.RequestId += 1; } // Clear Oids from the Pdu class. pdu.VbList.Clear(); // Initialize request PDU with the last retrieved Oid pdu.VbList.Add(lastOid); // Make SNMP request SnmpV1Packet result = (SnmpV1Packet)target.Request(pdu, param); // You should catch exceptions in the Request if using in real application. // If result is null then agent didn't reply or we couldn't parse the reply. if (result != null) { // ErrorStatus other then 0 is an error returned by // the Agent - see SnmpConstants for error definitions if (result.Pdu.ErrorStatus != 0) { // agent reported an error with the request Console.WriteLine("Error in SNMP reply. Error {0} index {1}", result.Pdu.ErrorStatus, result.Pdu.ErrorIndex); lastOid = null; break; } else { // Walk through returned variable bindings foreach (Vb v in result.Pdu.VbList) { // Check that retrieved Oid is "child" of the root OID if (rootOid.IsRootOf(v.Oid)) { Console.WriteLine("{0} ({1}): {2}", v.Oid.ToString(), SnmpConstants.GetTypeName(v.Value.Type), v.Value.ToString()); lastOid = v.Oid; } else { // we have reached the end of the requested // MIB tree. Set lastOid to null and exit loop lastOid = null; } } } } else { Console.WriteLine("No response received from SNMP agent."); } } target.Close(); } } } </pre> <h2>SNMP Version 2 GET-BULK Example</h2> <pre xml:lang="csharp">using System; using System.Net; using SnmpSharpNet; namespace sharpwalk { class Program { static void Main(string[] args) { // SNMP community name OctetString community = new OctetString("public"); // Define agent parameters class AgentParameters param = new AgentParameters(community); // Set SNMP version to 2 (GET-BULK only works with SNMP ver 2 and 3) param.Version = SnmpVersion.Ver2; // Construct the agent address object // IpAddress class is easy to use here because // it will try to resolve constructor parameter if it doesn't // parse to an IP address IpAddress agent = new IpAddress("127.0.0.1"); // Construct target UdpTarget target = new UdpTarget((IPAddress)agent, 161, 2000, 1); // Define Oid that is the root of the MIB // tree you wish to retrieve Oid rootOid = new Oid("1.3.6.1.2.1.2.2.1.2"); // ifDescr // This Oid represents last Oid returned by // the SNMP agent Oid lastOid = (Oid)rootOid.Clone(); // Pdu class used for all requests Pdu pdu = new Pdu(PduType.GetBulk); // In this example, set NonRepeaters value to 0 pdu.NonRepeaters = 0; // MaxRepetitions tells the agent how many Oid/Value pairs to return // in the response. pdu.MaxRepetitions = 5; // Loop through results while (lastOid != null) { // When Pdu class is first constructed, RequestId is set to 0 // and during encoding id will be set to the random value // for subsequent requests, id will be set to a value that // needs to be incremented to have unique request ids for each // packet if (pdu.RequestId != 0) { pdu.RequestId += 1; } // Clear Oids from the Pdu class. pdu.VbList.Clear(); // Initialize request PDU with the last retrieved Oid pdu.VbList.Add(lastOid); // Make SNMP request SnmpV2Packet result = (SnmpV2Packet)target.Request(pdu, param); // You should catch exceptions in the Request if using in real application. // If result is null then agent didn't reply or we couldn't parse the reply. if (result != null) { // ErrorStatus other then 0 is an error returned by // the Agent - see SnmpConstants for error definitions if (result.Pdu.ErrorStatus != 0) { // agent reported an error with the request Console.WriteLine("Error in SNMP reply. Error {0} index {1}", result.Pdu.ErrorStatus, result.Pdu.ErrorIndex); lastOid = null; break; } else { // Walk through returned variable bindings foreach (Vb v in result.Pdu.VbList) { // Check that retrieved Oid is "child" of the root OID if (rootOid.IsRootOf(v.Oid)) { Console.WriteLine("{0} ({1}): {2}", v.Oid.ToString(), SnmpConstants.GetTypeName(v.Value.Type), v.Value.ToString()); if (v.Value.Type == SnmpConstants.SMI_ENDOFMIBVIEW) lastOid = null; else lastOid = v.Oid; } else { // we have reached the end of the requested // MIB tree. Set lastOid to null and exit loop lastOid = null; } } } } else { Console.WriteLine("No response received from SNMP agent."); } } target.Close(); } } }
When I run either of the above examples on my notebook, I get following output:
1.3.6.1.2.1.2.2.1.2.1 (OctetString): Software Loopback Interface 1
1.3.6.1.2.1.2.2.1.2.2 (OctetString): WAN Miniport (L2TP)
1.3.6.1.2.1.2.2.1.2.3 (OctetString): WAN Miniport (PPTP)
1.3.6.1.2.1.2.2.1.2.4 (OctetString): WAN Miniport (PPPOE)
1.3.6.1.2.1.2.2.1.2.5 (OctetString): WAN Miniport (IPv6)
1.3.6.1.2.1.2.2.1.2.6 (OctetString): WAN Miniport (IP)
1.3.6.1.2.1.2.2.1.2.7 (OctetString): RAS Async Adapter
1.3.6.1.2.1.2.2.1.2.8 (OctetString): WAN Miniport (SSTP)
1.3.6.1.2.1.2.2.1.2.9 (OctetString): WAN Miniport (Network Monitor)
1.3.6.1.2.1.2.2.1.2.10 (OctetString): Realtek RTL8101E Family PCI-E FE NIC
1.3.6.1.2.1.2.2.1.2.11 (OctetString): Intel(R) PRO/Wireless 3945ABG Network Connection
1.3.6.1.2.1.2.2.1.2.12 (OctetString): Teredo Tunneling Pseudo-Interface
1.3.6.1.2.1.2.2.1.2.13 (OctetString): VMware Virtual Ethernet Adapter for VMnet1
1.3.6.1.2.1.2.2.1.2.14 (OctetString): isatap.{5D34A036-E8DD-4344-9CE3-58B461D1CB2A}
1.3.6.1.2.1.2.2.1.2.15 (OctetString): VMware Virtual Ethernet Adapter for VMnet8
1.3.6.1.2.1.2.2.1.2.16 (OctetString): isatap.{9195335D-923C-42BB-AC88-F54BC4501539}
1.3.6.1.2.1.2.2.1.2.17 (OctetString): isatap.hsd1.il.comcast.net.
1.3.6.1.2.1.2.2.1.2.18 (OctetString): Microsoft 6to4 Adapter
1.3.6.1.2.1.2.2.1.2.19 (OctetString): Microsoft ISATAP Adapter
1.3.6.1.2.1.2.2.1.2.20 (OctetString): WAN Miniport (IPv6)-QoS Packet Scheduler-0000
1.3.6.1.2.1.2.2.1.2.21 (OctetString): WAN Miniport (IP)-QoS Packet Scheduler-0000
1.3.6.1.2.1.2.2.1.2.22 (OctetString): WAN Miniport (Network Monitor)-QoS Packet Scheduler-0000
1.3.6.1.2.1.2.2.1.2.23 (OctetString): Realtek RTL8101E Family PCI-E FE NIC-QoS Packet Scheduler-0000
1.3.6.1.2.1.2.2.1.2.24 (OctetString): Intel(R) PRO/Wireless 3945ABG Network Connection-QoS [..]
1.3.6.1.2.1.2.2.1.2.25 (OctetString): Intel(R) PRO/Wireless 3945ABG Network Connection-Native [...]
SNMP Get request is used to retrieve individual MIB values. To make a Get request you need to know the OID of the value you wish to retrieve.
Example retrieves 5 MIB values. In a real application you can request values for 1 or more OIDs. Keep in mind that response size has to be up to maximum packet size bytes so don't request too many variables in a single packet.
using System; using System.Net; using SnmpSharpNet; namespace snmpget { class Program { static void Main(string[] args) { // SNMP community name OctetString community = new OctetString("public"); // Define agent parameters class AgentParameters param = new AgentParameters(community); // Set SNMP version to 1 (or 2) param.Version = SnmpVersion.Ver1; // Construct the agent address object // IpAddress class is easy to use here because // it will try to resolve constructor parameter if it doesn't // parse to an IP address IpAddress agent = new IpAddress("127.0.0.1"); // Construct target UdpTarget target = new UdpTarget((IPAddress)agent, 161, 2000, 1); // Pdu class used for all requests Pdu pdu = new Pdu(PduType.Get); pdu.VbList.Add("1.3.6.1.2.1.1.1.0"); //sysDescr pdu.VbList.Add("1.3.6.1.2.1.1.2.0"); //sysObjectID pdu.VbList.Add("1.3.6.1.2.1.1.3.0"); //sysUpTime pdu.VbList.Add("1.3.6.1.2.1.1.4.0"); //sysContact pdu.VbList.Add("1.3.6.1.2.1.1.5.0"); //sysName // Make SNMP request SnmpV1Packet result = (SnmpV1Packet)target.Request(pdu, param); // If result is null then agent didn't reply or we couldn't parse the reply. if (result != null) { // ErrorStatus other then 0 is an error returned by // the Agent - see SnmpConstants for error definitions if (result.Pdu.ErrorStatus != 0) { // agent reported an error with the request Console.WriteLine("Error in SNMP reply. Error {0} index {1}", result.Pdu.ErrorStatus, result.Pdu.ErrorIndex); } else { // Reply variables are returned in the same order as they were added // to the VbList Console.WriteLine("sysDescr({0}) ({1}): {2}", result.Pdu.VbList[0].Oid.ToString(), SnmpConstants.GetTypeName(result.Pdu.VbList[0].Value.Type), result.Pdu.VbList[0].Value.ToString()); Console.WriteLine("sysObjectID({0}) ({1}): {2}", result.Pdu.VbList[1].Oid.ToString(), SnmpConstants.GetTypeName(result.Pdu.VbList[1].Value.Type), result.Pdu.VbList[1].Value.ToString()); Console.WriteLine("sysUpTime({0}) ({1}): {2}", result.Pdu.VbList[2].Oid.ToString(), SnmpConstants.GetTypeName(result.Pdu.VbList[2].Value.Type), result.Pdu.VbList[2].Value.ToString()); Console.WriteLine("sysContact({0}) ({1}): {2}", result.Pdu.VbList[3].Oid.ToString(), SnmpConstants.GetTypeName(result.Pdu.VbList[3].Value.Type), result.Pdu.VbList[3].Value.ToString()); Console.WriteLine("sysName({0}) ({1}): {2}", result.Pdu.VbList[4].Oid.ToString(), SnmpConstants.GetTypeName(result.Pdu.VbList[4].Value.Type), result.Pdu.VbList[4].Value.ToString()); } } else { Console.WriteLine("No response received from SNMP agent."); } target.Close(); } } }
When I run the above example on my notebook, I get following output:
sysDescr(1.3.6.1.2.1.1.1.0) (OctetString): "Dual core Intel notebook" sysObjectID(1.3.6.1.2.1.1.2.0) (ObjectId): 1.3.6.1.9.233.1.1 sysUpTime(1.3.6.1.2.1.1.3.0) (TimeTicks): 0d 1h 50m 28s 280ms sysContact(1.3.6.1.2.1.1.4.0) (OctetString): "dev@snmpsharpnet.com" sysName(1.3.6.1.2.1.1.5.0) (OctetString): "milans-nbook"
When making an SNMP Set request, you need to supply a pair of values in your Pdu, OID that you wish to change, and the value to change it to. To be able to change an OID value, first that OID needs to be read-write (you can find this out by reading the MIB files and checking the ACCESS value) and you will need to know what kind of value that OID will accept. It is important to send the right kind of value to the agent to perform a Set operation. If you send a wrong value, for example a OctetString to an OID that accepts Integer32 values, agent will return WrongType error in the SnmpPacket.Pdu.ErrorStatus variable.
In this example we are changing sysLocation.0 MIB variable value. sysLocation.0 takes a value of type OctetString.
using System; using SnmpSharpNet; using System.Net; namespace CSharpSet { class Program { static void Main(string[] args) { // Prepare target UdpTarget target = new UdpTarget((IPAddress)new IpAddress("some-host-name")); // Create a SET PDU Pdu pdu = new Pdu(PduType.Set); // Set sysLocation.0 to a new string pdu.VbList.Add(new Oid("1.3.6.1.2.1.1.6.0"), new OctetString("Some other value")); // Set Agent security parameters AgentParameters aparam = new AgentParameters(SnmpVersion.Ver2, new OctetString("private")); // Response packet SnmpV2Packet response; try { // Send request and wait for response response = target.Request(pdu, aparam) as SnmpV2Packet; } catch (Exception ex) { // If exception happens, it will be returned here Console.WriteLine(String.Format("Request failed with exception: {0}", ex.Message)); target.Close(); return; } // Make sure we received a response if (response == null) { Console.WriteLine("Error in sending SNMP request."); } else { // Check if we received an SNMP error from the agent if (response.Pdu.ErrorStatus != 0) { Console.WriteLine(String.Format("SNMP agent returned ErrorStatus {0} on index {1}", response.Pdu.ErrorStatus, response.Pdu.ErrorIndex)); } else { // Everything is ok. Agent will return the new value for the OID we changed Console.WriteLine(String.Format("Agent response {0}: {1}", response.Pdu[0].Oid.ToString(), response.Pdu[0].Value.ToString())); } } } } }
Imports SnmpSharpNet Module Module1 Sub Main() ' Prepare target Dim target = New UdpTarget(New IpAddress("some-host"), 161, 1000, 0) ' Create a SET PDU Dim pdu = New Pdu(PduType.Set) ' Set sysLocation.0 to a new string pdu.VbList.Add(New Oid("1.3.6.1.2.1.1.6.0"), New OctetString("Test description")) ' Set Agent security parameters Dim aparam = New AgentParameters(SnmpVersion.Ver2, New OctetString("private")) ' Response packet Dim response As SnmpV2Packet Try ' Send request and wait for response response = target.Request(pdu, aparam) Catch ex As Exception ' If exception happens, it will be returned here Console.WriteLine(String.Format("Exception: {0}", ex.Message)) target.Close() Return End Try ' Make sure we received a response If response Is Nothing Then Console.WriteLine("Error in sending SNMP request.") Else ' Check if we received an SNMP error from the agent If response.Pdu.ErrorStatus <> 0 Then Console.WriteLine(String.Format("SNMP agent returned ErrorStatus {0} on index {1}", response.Pdu.ErrorStatus, response.Pdu.ErrorIndex)) Else ' Everything is ok. Agent will return the new value for the OID we changed Console.WriteLine(String.Format("Agent response {0}: {1}", response.Pdu(0).Oid.ToString(), response.Pdu(0).Value.ToString())) End If End If target.Close() End Sub End Module
using System using System.Net; using System.Net.Sockets; using SnmpSharpNet; namespace traprecv { class Program { static void Main(string[] args) { // Construct a socket and bind it to the trap manager port 162 Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); IPEndPoint ipep = new IPEndPoint(IPAddress.Any, 162); EndPoint ep = (EndPoint)ipep; socket.Bind(ep); // Disable timeout processing. Just block until packet is received socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 0); bool run = true; while (run) { byte[] indata = new byte[16 * 1024]; // 16KB receive buffer int inlen = 0; IPEndPoint peer = new IPEndPoint(IPAddress.Any, 0); EndPoint inep = (EndPoint)peer; try { inlen = socket.ReceiveFrom(indata, ref inep); } catch( Exception ex ) { Console.WriteLine("Exception {0}", ex.Message); inlen = -1; } if (inlen > 0) { // Check protocol version int ver = SnmpPacket.GetProtocolVersion(indata, inlen); if (ver == (int)SnmpVersion.Ver1) { // Parse SNMP Version 1 TRAP packet SnmpV1TrapPacket pkt = new SnmpV1TrapPacket(); pkt.decode(indata, inlen); Console.WriteLine("** SNMP Version 1 TRAP received from {0}:", inep.ToString()); Console.WriteLine("*** Trap generic: {0}", pkt.Pdu.Generic); Console.WriteLine("*** Trap specific: {0}", pkt.Pdu.Specific); Console.WriteLine("*** Agent address: {0}", pkt.Pdu.AgentAddress.ToString()); Console.WriteLine("*** Timestamp: {0}", pkt.Pdu.TimeStamp.ToString()); Console.WriteLine("*** VarBind count: {0}", pkt.Pdu.VbList.Count); Console.WriteLine("*** VarBind content:"); foreach (Vb v in pkt.Pdu.VbList) { Console.WriteLine("**** {0} {1}: {2}", v.Oid.ToString(), SnmpConstants.GetTypeName(v.Value.Type), v.Value.ToString()); } Console.WriteLine("** End of SNMP Version 1 TRAP data."); } else { // Parse SNMP Version 2 TRAP packet SnmpV2Packet pkt = new SnmpV2Packet(); pkt.decode(indata, inlen); Console.WriteLine("** SNMP Version 2 TRAP received from {0}:", inep.ToString()); if (pkt.Pdu.Type != (byte)PduType.V2Trap) { Console.WriteLine("*** NOT an SNMPv2 trap ****"); } else { Console.WriteLine("*** Community: {0}", pkt.Community.ToString()); Console.WriteLine("*** VarBind count: {0}", pkt.Pdu.VbList.Count); Console.WriteLine("*** VarBind content:"); foreach (Vb v in pkt.Pdu.VbList) { Console.WriteLine("**** {0} {1}: {2}", v.Oid.ToString(), SnmpConstants.GetTypeName(v.Value.Type), v.Value.ToString()); } Console.WriteLine("** End of SNMP Version 2 TRAP data."); } } } else { if (inlen == 0) Console.WriteLine("Zero length packet received."); } } } } }
Imports SnmpSharpNet Imports System.Threading Imports System.Net.Sockets Imports System.Net Imports System.IO Module Module1 Sub Main() Dim socket As Socket = New Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp) Dim ipep As IPEndPoint = New IPEndPoint(System.Net.IPAddress.Any, 162) socket.Bind(ipep) Dim run As Boolean = True While True Dim inbuf() As Byte = New [Byte](16 * 1024) {} Dim peer As IPEndPoint = New IPEndPoint(System.Net.IPAddress.Any, 0) Dim inep As EndPoint = peer Dim inlen As Integer Try inlen = socket.ReceiveFrom(inbuf, 0, inbuf.Length, SocketFlags.None, inep) Catch ex As Exception Console.WriteLine("Exception: " & ex.Message) inlen = -1 End Try If inlen > 0 Then Dim ver As Integer = SnmpPacket.GetProtocolVersion(inbuf, inlen) If ver = SnmpVersion.Ver1 Then Dim pkt As SnmpV1TrapPacket = New SnmpV1TrapPacket() pkt.decode(inbuf, inlen) Console.WriteLine("** SNMP Version 1 TRAP received from {0}:", inep.ToString()) Console.WriteLine("*** Trap generic: {0}", pkt.Pdu.Generic) Console.WriteLine("*** Trap specific: {0}", pkt.Pdu.Specific) Console.WriteLine("*** Agent address: {0}", pkt.Pdu.AgentAddress.ToString()) Console.WriteLine("*** Timestamp: {0}", pkt.Pdu.TimeStamp.ToString()) Console.WriteLine("*** VarBind count: {0}", pkt.Pdu.VbList.Count) Console.WriteLine("*** VarBind content:") Dim v As Vb For Each v In pkt.Pdu.VbList Console.WriteLine("**** {0} {1}: {2}", v.Oid.ToString(), SnmpConstants.GetTypeName(v.Value.Type), v.Value.ToString()) Next Console.WriteLine("** End of SNMP Version 1 TRAP data.") ElseIf ver = SnmpVersion.Ver2 Then Dim pkt As SnmpV2Packet = New SnmpV2Packet() pkt.decode(inbuf, inlen) Console.WriteLine("** SNMP Version 2 TRAP received from {0}:", inep.ToString()) If (pkt.Pdu.Type <> PduType.V2Trap) Then Console.WriteLine("*** NOT an SNMPv2 trap ****") Else Console.WriteLine("*** Community: {0}", pkt.Community.ToString()) Console.WriteLine("*** VarBind count: {0}", pkt.Pdu.VbList.Count) Console.WriteLine("*** VarBind content:") Dim v As Vb For Each v In pkt.Pdu.VbList Console.WriteLine("**** {0} {1}: {2}", v.Oid.ToString(), SnmpConstants.GetTypeName(v.Value.Type), v.Value.ToString()) Next Console.WriteLine("** End of SNMP Version 2 TRAP data.") End If Else Console.WriteLine("Invalid protocol version number {0}", ver.ToString()) End If Else If inlen = 0 Then Console.WriteLine("Zero length packet received.") End If End If End While End Sub End Module
C:\>traprecv ** SNMP Version 2 TRAP received from 127.0.0.1:64416: *** Community: public *** VarBind count: 2 *** VarBind content: **** 1.3.6.1.2.1.1.5.0 OctetString: MyHost **** 1.3.6.1.2.1.1.1.0 OctetString: Host do you like my trap ** End of SNMP Version 2 TRAP data. ** SNMP Version 1 TRAP received from 127.0.0.1:64417: *** Trap generic: 3 *** Trap specific: 0 *** Agent address: 127.0.0.1 *** Timestamp: 432d 21h 8m 0s 550ms *** VarBind count: 1 *** VarBind content: **** 1.3.6.1.2.1.1.5.0 OctetString: MyHost ** End of SNMP Version 1 TRAP data.Commands used to generate traps:
C:\>snmptrap -v 2c -c public localhost .1.3.6.1.2.1.1 localhost \
sysName.0 s "MyHost" sysDescr.0 s "Host do you like my trap"
C:\>snmptrap -v 1 -c public localhost .1.3.6.1.2.1.1 localhost 3 0 "" sysName.0 s "MyHost"