Introduction to Plug-ins

What is a Plug-in?

A plug-in is a dll that extends the capabilities of the Omnipeek product family. Primarily, plug-ins analyze packets. Plug-ins can also analyze statistics as well as events. In fact, plug-ins can really analyze anything, even data that is external to Omnipeek. As a result of analysis, plug-ins feed information of various forms back into Omnipeek.

Any number of plug-ins can be run together at the same time. Plug-ins can even be built as Legos, where one plug-in does some analysis and then feeds something back into Omnipeek that another plug-in analyzes.

Plug-ins can also insert both ethernet and wireless packets into Omnipeek. This type of plug-in is called a Remote Adapter. RMONGrabber, RFGrabber, and the TCPDump Adapter are all examples of Remote Adapters.

Where do Plug-ins live?

Plug-ins live in the plug-ins folder found in the Omnipeek install folder. For the default installation of the Omnipeek Console, this would be: C:\Program Files\LiveAction\Omnipeek\Plug-ins.

Some plug-ins depend on other dll's. These dll's can either be put in the plug-ins folder or copied into the Windows\System32 directory. Some plug-ins contain COM components. These species of plug-ins have to be registered with Windows. This is done be running "regsvr32 myPlugin.dll".

What is the Plug-in API?

Omnipeek and plug-ins communicate through a well defined API, which is basically a set of function calls. There are two types of functions in the Plug-in API: Messages and Callbacks.

Messages are functions that call from Omnipeek into the plug-in. The reason these functions are called messages is because there is actually only one well know function that calls into a plug-in from Omnipeek. It is the all-purpose function which takes a message type and some opaque data. On the Omnipeek side, all messages are called through this message function. Inside the plug-in, the messages are branched into specific message handlers, which in turn call C++ virtual functions. All of this is taken care of by the plug-in Framework which is generated by the Plug-in Wizard. Message functions are all prefixed with "On". So message functions have names like OnThis() or OnThat().

Callbacks are functions that call from the plug-in into Omnipeek. Callbacks provide plug-ins with access to facilities that Peek has like the Packet Buffer, Summary Stats, and the Name Table. Callback functions are all prefixed with "Do". So callbacks have names like DoThis() and DoThat().

Getting Started

Getting Started with plug-ins is simple but does require at least some C/C++ programming experience.

On the C side you should understand things like structures, pointers, memory management, arrays, strings, maps, and preprocessor stuff like macros. You should also know about the differences between Unicode and ASCII since the plug-ins are built in Unicode (which is double byte) while most of the strings you find in packets are single byte. This requires lots of conversions, which are easy to do with macros, as long as you know to do them. Also, the more you know about design patterns and software architecture, the less of a rats nest you are going to build yourself into.

On the C++ side you should know some things about object oriented programming like classes, new and delete, virtual functions and polymorphism, constructors and destructors. Again the more you know about architecture, the better. Like a good friend of mine always said, "it is not about commenting bad code, it is about writing good code in the first place." In other words, write simple code, don't nest too deeply, initialize your variable, check all your pointers, and ASSERT constantly. Oh yeah, and don't leak memory! There is no such thing as a good memory leak. And anyway, it is embarrassing and easily avoidable.

Conventions

During the development of a plug-in, you can become very intimate with your plug-in. No, not romantically intimate, but rather intimately familiar. Because of this, plug-in programmers tend to refer to themselves and their plug-ins interchangeably. So we do that here as well. We will often refer to you and your plug-in as the same thing.

We also use the term Foo. Foo represents whatever the name of your plug-in is really called.

OnThis() and DoThat() functions are always in the context of CFooContext, unless specified otherwise.

The Plug-in Wizard

The Plug-in Wizard is a Dev Studio Wizard that generates Omnipeek plug-ins with sample code. The Plug-in Wizard requires Dev Studio 7 and 2005 Enterprise. The Wizard does not work with Dev Studio 6 or Dev Studio 2005 Express.

A plug-in generated by the Plug-in Wizard includes a Solution, a Project, a set of C++ framework classes, and the sample code that you select. The framework classes provide setup of the plug-in as well as specific classes and functions to add your specific application code.

Using the Plug-in Wizard

To use the Plug-in Wizard just download it from MyPeek and run the installer. The Plug-in Wizard will install into Dev Studio. Once it is installed, run Dev Studio and create a new Project using the "Peek Plug-in 11" project template. The Plug-in Wizard will then present a dialog that lists the various sample code that can be included in your plug-in. Each sample is enabled with a checkbox. For example, if you want your own tab in the Capture Window, enable the tab sample. If you want to process notifications, enable the Notification sample.

Once all of the desired samples have been selected, if any, hit the OK button, and a project will be created with Debug and Release build configurations. Keep in mind that Omnipeek plug-ins are Unicode plug-ins. This means you have to use the TCHAR routines and lots of T2A() and A2T() macros. This is really very simple though and the Plug-in Wizard generates plenty of sample code to start from.

Running the Plug-in in Peek

Once the Plug-in Wizard has generated the Plug-in Project you can build it. The result is a dll in either the Release or Debug folder. To install the plug-in, copy it to the Plug-ins folder of your Omnipeek installation folder. The next time you run Omnipeek, your plug-in will be there.

To check whether your plug-in got loaded, go to the Tools->Options->Analysis Modules dialog. If your plug-in is not listed then it did not get loaded. There are many reasons why this can happen. The most common is because the plug-in is linking with another dll that it cannot find. To find out what dependencies your plug-in has, run depends.exe on it.

Debugging the Plug-in in Peek

There are two ways to debug your Plug-in. One way is to run Peek and connect to the process from the debugger. The other way is to run Peek from the debugger in the first place. Both techniques are useful and which one you use will depend on the situation. For starters though, I always add a statement in the Post Build Commands section of the Project Properties that copies the dll to the Omnipeek Plug-ins directory. I then start the debugger and navigate to the Opeek.exe program.

Capture Windows

Every time a new Capture Window is created in Omnipeek, a plug-in is asked if it would like to create a new Context object for that Capture Window. In other words, for each plug-in there is a context object for each capture window.

The type of capture window is specified as a parameter in the call to CFooPlugin::OnCreateContext(). This is one of the Message functions called by Omnipeek into the plug-in. One of the parameters is a pointer to the Context Object that is created by the plug-in. If the plug-in does not create a Context Object, this pointer should be set to NULL.

In Omnipeek there are four types of Capture Windows: Monitor (or Global), Real-time Window, File Window, and Remote Capture Window.

Monitor / Global

The Global Context is the Monitor. There is only Monitor and if the Monitor is on it is always the first context to be created. The Monitor does not have a Window associated with it so it cannot provide a tab. This means that plug-ins which create their own tab should not create a Context Object for the Monitor. Monitor Contexts can process packets and callback into Omnipeek with results.

Real-time Window

Real-Time Capture Windows have a network adapter and a capture buffer associated with them. Real-time Capture Windows can be started and stopped. When a Real-time Capture is started the Plug-in Context Object receives a reset message through the function OnReset() followed by a start message through the function OnStartCapture(). When the capture is stopped, the function OnStopCapture() is called.

File Window

File Windows have static size capture buffers. After the packets from a file are loaded into the capture buffer, the packets are sent through the plug-in as ProcessPacket messages. These messages are handled through the OnProcessPacket() function. When all of the packets have been processed the function is OnPacketsLoaded() is called.

Remote Capture Window

Remote Capture Windows are associated with Capture Engine Captures. This type of Capture Window does not process packets; instead, it interacts with an Capture Engine Server plug-in through a messaging function provided by the Plug-in API. Remote Capture Windows are beyond the scope of this document. More information about Remote Capture Windows can be found on MyPeek as well as a sample called OmniPing.

Plug-in Framework Objects

There are a couple of C++ classes that are generated by the Plug-in Wizard and many more can be created by you during the development of your plug-in. Two of the more important classes are the Plug-in class and the Context class.

2006-08-25_image1.gif

The Plug-in Object

In each plug-in there is one and only one plug-in object. If Foo is the name of your plug-in, then your plug-in object is called CFooPlugin. CFooPlugin is the main message handler. It maintains the list of Context Objects and routes messages to them.

Although most messages from Omnipeek get routed to a particular Context Object, there are certain messages that the plug-in object handles itself. For example, it handles OnCreateContext() which decides which creates context objects. It also handles message like OnLoad(), OnUnload(), OnReadPrefs(), and OnWritePrefs();

The Context Object

The Context Object is one of the Plug-in Framework Classes. It is where the Plug-in Message APIs are routed to and where state about that capture window is maintained. Of course, the Context Object may create any number of other objects, like Dialogs, that contain state and functions as well.

Because the messages received by the OnThis() and OnThat() functions of the Context Object are always called on the main thread of Omnipeek, it is extremely important to do what you have to do and return from the functions as quickly as possible. While these functions are running, the UI is not doing anything. If your plug-in has to do something that may take a while, then either perform that task on a different thread or call the UI message pump often enough to keep the UI going.

If the name of your plug-in is Foo, then the Context object for your plug-in is called CFooContext. This plug-in specific context object is always derived from a base class called CPeekContext. CPeekContext is where all of the Message and Callback functions are defined. The Callback functions implemented by CPeekContext provide a bit of convenience by supplying some of the parameters. The Message functions are virtual so that they can be overloaded in the CFooContext.

Packets

The heart of most plug-ins is the packet processor. This is where you get to show off your knowledge of a particular protocol by decoding it. In some cases protocols are straightforward and can be decoded by casting some number of bytes at an offset in the packet to either an 8, 16, 32, or 64 bit variable. Or maybe just copy some number of bytes to a character buffer. But of course, it is rarely that straightforward, particularly the IEEE protocols. And, I am truly sorry for the engineer who has to decode anything encoded in ASN.1! A few years ago, I was that engineer and my brain still hurts.

Processing Packets

There are two functions that process packets in real-time: OnProcessPacket() and OnFilter(). OnProcessPacket() is called for every packet. Returning -1 from OnProcessPacket() will stop the capture. OnFilter() is used to filter packets. If your plug-in is set up as an advanced filter and that filter is enabled, then your plug-in will receive OnFilter() messages for every packet. If OnFilter() returns false, the packet will not be processed any further and will not go into the capture buffer.

The parameters for OnProcessPacket() and OnFilter() functions contain information about a packet as well as a pointer to the packet data itself. There are numerous Callback functions that can be used to ask questions about the packet and to quickly return pointers to different layers of the packet. Samples of these functions are generated by the Plug-in Wizard.

Do not change the packet data! But then again, if you do, be aware that you are changing the same packet data that other parts of the program are processing as well. In most cases, if you need to manipulate the packet data before processing it (like replacing binary characters with dots in order to do a string search), then create your own buffer and copy the packet data there where you can safely mess with it. Keep in mind that putting a static size buffer on the stack takes much less time than putting one on the heap and then destroying it.

It is a very good idea to use try/catch blocks around your packet processing code because if your code were to throw an exception that was not caught, the capture thread would die and no more packets would be captured.

Capture Control

When a capture is started, the OnStartCapture() function is called. If for some reason the plug-in decides capture should not be started then it can return -1. This will halt the start capture process. The plug-in should also probably put up a dialog box informing the user why capture did not start. But be careful with modal dialog boxes. Scripts using the Peek COM API to automate captures do not appreciate dialog boxes that appear out of nowhere demanding user input.

OnStartCapture() is a good place to set up a Boolean like m_bCapturing to TRUE, and OnStopCapture() is where you can set it to FALSE. This Boolean can be used by other functions in the plug-in that need to know whether capturing is running or not. For example, OnSummary() is a function that is called once every second (as long as Summary Statistics is turned on). This function can be used to do things that need to be run on the main thread. And by the way, there are a lot of things that need to be done on the main thread, mostly windows and COM related.

File Windows do not call OnStartCapture() and OnStopCapture(). Instead, the packets just start coming in through OnProcessPacket(), and OnPacketsLoaded() is called when they are done.

Inserting Packets

Plug-ins can insert packets through the DoInsertPacket() callback. This is often very useful for two reasons. One is for Remote Adapters which collect packets from external sources and insert them into Omnipeek. Remote Adapters are beyond the scope of this document and are not very well documented. However, there are sample remote adapters in source code form that can be downloaded from MyPeek. The other reason is to filter out packets and re-assemble them into different packets. This is useful for extracting packets out of tunnels, decrypting them, or uncompressing packets.

Packet Buffer

Omnipeek has a packet buffer that keeps some number of packets. The size of the packet buffer is configured by the user and if the user chooses continuous capture, then when the packet buffer fills up the packets start falling off the end of it. With this in mind, the Plug-in API provides functions to iterate through the packets in the packet buffer. The first call to make is always DoGetFirstPacketIndex(). This is the first packet still in the buffer. Following that, DoGetPacket() can be used to loop through or randomly access packets in the buffer.

Sending Packets

Plug-ins can send packets through the Send Adapter using the Plug-in API. The PeekPlayer Plug-in is a good example of this. Keep in mind that the Send Adapter has to be manually set through the UI.

Protospecs

Plug-ins are usually written to process certain types of packets. For example, let's say you want to write a plug-in to process SMTP and POP3 packets. In this case you only want the plug-in to receive SMTP and POP3 packets. You would rather not have to test each packet yourself because you know there could be shims and tunnels and all kinds of things in the packet headers above the SMTP part which means that you cannot just hard code an offset to check port 25. To make this part easy, the plug-in can specify what kinds of packets it is interested in by setting a static variable in CFooPlugin.cpp to a list of protocols:

// Static class members.

UInt32 CFooPlugin::s_SupportedProtoSpecs[] =

{

	  ProtoSpecDefs::SMTP,

	  ProtoSpecDefs::POP3

};

Packet Layer Callbacks

Again, plug-ins are usually written to process certain types of packets. No matter how much filtering you do, let's say for SMTP or POP3 packets, when you receive the packet for processing you get the whole packet. But, you probably do not want the whole packet, do you? Most likely, you and your plug-in are only interested in the SMTP or POP3 layer.

To get a pointer to the part of the packet where a specific protocol layer starts, the Plug-in API provides a packet layer callback called DoPacketGetLayer();

Reporting

Plug-ins have numerous functions that can be used to feed data back into Omnipeek. Plug-ins can also provide their own reports as well.

Summary Statistics

The result of processing packets is often times a statistic of some sort. Omnipeek provides a facility called Summary Statistics which maintains groups of statistics names and their values. The Plug-in API provides functions to get and set summary statistics. These functions are called DoSummaryModifyEntry() and DoSummaryGetEntry(). Using the Summary Statistics facility in Omnipeek is highly leveraged because they can be saved, printed, graphed, as well as other operations that can be performed.

This makes it possible to use the Summary Statistics facility as a map of stats, searchable by name, instead of implementing your own. Also, because the same Summary Stats table is shared by all of the plug-ins, it can be used to pass values between plug-ins. This makes it possible for plug-ins to work together where one plug-in sets a value and another one gets it. There are also lots of statistics provided by the core product that plug-ins can leverage.

Notifications

There are many facilities in Omnipeek that generate notifications. The Expert is a good example of that. In fact, the Expert can be quite noisy with the default settings, generating a lot of notifications, and resulting in a lot of log entries.

Plug-ins can receive notifications through the message handler function called OnHandleNotify(). This function passes a severity, a timestamp, a short message, and a long message.

Plug-ins can generate notifications as well through the callback function DoNotify(). The PeekWebURL Plug-in, which ships with Omnipeek, is a good example. The PeekWebURL Plug-in processes HTTP GET and POST requests and generates a notification for each.

Name Table

Omnipeek has a built in name table, and the Plug-in API provides functions to interact with it. This is a good resource to use, but keep in mind that the name table gets read into memory every time you start Omnipeek so be careful about adding too many names.

Plug-in UI

Global Options

For plug-ins that allow the user to change options, the Plug-in Wizard generates a sample Options Dialog. User Interface components can be easily added to the sample Options Dialog through the Dev Studio Resource Editor.

There are two common ways to save and restore options from disk. The old fashioned way is to use the registry. Two callback functions are provided for this: DoPrefsGetValue() and DoPrefsSetValue(). The limitation of this technique is that the options cannot be shared between users and plug-ins on different machines. In addition, saving lists of things in the registry is not very practical. The alternative method is to save the options to XML formatted files. Code for reading and writing XML files is not generated by the wizard; however, there are numerous plug-ins on MyPeek that contain a set of simple XML classes that are used for this purpose.

Creating a Tab

For plug-ins that have per capture options, or options that are different for each capture, a tab can be created using the DoAddTab() callback. The wizard provides two types of sample tabs.

One of the sample tabs is the ATL Tab which is an ATL object with a COM interface. If you are a COM junky or have existing COM objects that you would like use in your plug-in, this is the way to go. Keep in mind though that the use of a COM object requires very specific interfaces and a very specific set of types that can be passed to and from the COM object.

The other sample tab is the MFC Dialog route. And ah, the simplicity that it is. With an MFC dialog you can easily create functions on demand and use the same MFC UI message handles that most of us have been living and breathing for years. Of course, some of us have been living and choking on the ATL interfaces as well, and I am sure after a couple of years of therapy we will be fine.

But anyway, although MFC and COM and ATL are all way beyond the scope of this primer, I am going to talk about the MFC technique for adding a tab just a bit. So here goes. After using the wizard to generate your plug-in with an MFC Tab, the first thing you are going to want to do is add some UI components to it. As you add those UI components, you will also need to add code to the OnSize() handler of your Tab class to change the size of your components as the size of the tab changes. You are then probably going to add some buttons, checkboxes, edit fields, menus, and maybe even a toolbar. Code to handle these UI components is all over the place, which is the perfect segue to the next section.

Options Revisited

If you create a tab to allow the user to specify options, you have to decide if the same set of option values is going to be shared by every capture window or if each capture window is going to have its own copy of the options. One way to go about this is to display the same options in the Global Options Dialog as you do in each Capture Window. The options in the global options dialog are the ones that get saved and read while the options in each capture window are initialized with the Global Options but maintain their own copy. One advantage of having separate copies is that you do not need to worry about one capture accessing an option while another is deleting. Of course, this is only a problem if you have multiple threads accessing options at the same time. In that case, just use critical sections, but beware of deadlocks.

Miscellaneous

Cut and Paste

The good news is that you are not the first person to write a plug-in. There are lots of plug-ins MyPeek with source code for you to copy and paste. The smartest thing you can do is download every plug-in out there and familiarize yourself with what features they have and what code they have to implement those features. For example, let's say you want a Toolbar with tool tips. First use the resource editor to create the toolbar, then stop. Next find another plug-in that has a Toolbar. This is simple. Just search through the files for m_Toolbar. That is what we all call our toolbars. When you find a plug-in that has an m_Toolbar just copy all the code it uses to manage the toolbar to your plug-in.

Another good example is reassembling packets. Reassembling packets requires the use of a hash table and a bunch of algorithms for using the hash. There are already plug-ins out there that do this. WebStats is a good example and there are others as well.

Utility Classes

MyPeek includes a library of extensive and very useful classes called the shared library. As you become familiar with the source code of the plug-ins on MyPeek, you will see the classes from this library popping up over and over again. These classes can be built into a library and statically linked with your plug-in. However, I prefer to copy the classes from the shared library into my plug-in folder. I do this so that my plug-ins have as few dependencies as possible. I also think that in many cases the problems that arise from shared code outweigh the advantages. That is just my own philosophy though, and you may want to link with the shared library instead, which will work just fine.

X Classes

One suite of classes in the shared library is prefixed with X's. The X classes are all derived from a base class called XObject and make heavy use of X macros. The primary feature of the X classes is that they support abstract interfaces in a way that is very similar to COM or Corba, two of the most well known interface based class libraries. In fact, the X classes were originally developed on Unix and ported to Windows. Enough trivia, the X classes were found to be very useful as architectural tools in a lot of plug-ins and you may find them useful as well.

Further Reading

Go straight to MyPeek and download everything you can: https://mypeek.wildpackets.com

Build the plug-ins, use the tools, read the articles, and sign up for the MyPeek developer mailing list Install the Plug-in Wizard, generate a plug-in, and implement something interesting. If you get stuck, send us an email. We usually know the answer.