Wednesday, December 16, 2009

Driver to Service communication- Don’t reinvent the wheel!

Most of us have at one time or the other implemented "something" that was required for the communication between the user mode service and the kernel mode driver. Things are pretty straightforward when the communication is triggered from the service. For example, the service wants to send a list of files to be excluded from whatever magic the driver is doing. Now, let's talk about the communication originating from the driver. What then? Those who have implemented something like this know that it is not as straightforward as the previous case. Ofcoure, if you are writing a file system minifilter, then you are good. Just call FltSendMessage and you are done.

So, let's see if we can make use of the filter manager's communication port functionality to send and receive messages in our driver even if our driver is not a FS minifilter. Let's try to divide the functionality provided by filter manager in two categories: FS filtering and communication. If you observe carefully, both of them are independent of each other. However, the initial call to register the driver with the filter manager does check some registry entries that are specific to a FS minifilter; for example the altitude. Anyways, I tried to write a driver that received process notifications using the Process manager callbacks. My objective was to be able to receive the callback and send the information to the user mode service. So, I started off by using the INF of nullfilter minifilter sample.

Note that the driver that I tried to write was a "no stack" driver- meaning that it was not attached to network stack or storage stack, etc. Still need to figure out if that will work. Anyways, coming back to the driver, I cooked up the FLT_REGISTRATION structure in the following fashion:

CONST FLT_REGISTRATION registrationData =

{

sizeof (FLT_REGISTRATION),

FLT_REGISTRATION_VERSION,

0,

NULL,

NULL,

FooUnload,

NULL,

NULL,

NULL,

NULL,

NULL,

NULL,

NULL,

};


If you observe carefully, the only non-NULL parameter in this structure (except for the first two) is the FooUnload where I have registerd the unload routine. The idea of using this kind of registration structure was to tell the filter manager that we are not interested in any FS I/O callbacks (create, read, write, etc.) or in attaching to any volumes. Note that the 7th parameter is NULL (used to specify the Instance setup callback) but it does not mean that the filter manager will not create instance corresponding to this filter for the volumes. To be able to tell filter manager not to attach to any volume, you need to register an Instance setup callback and then return STATUS_FLT_DO_NOT_ATTACH for all the instance setup callbacks. I had other reasons for letting filter manager create the instances for me, so set it to NULL. Next interesting thing to note here is that you must specify a unload routine in the registration structure. And this where things change a bit. The signature of the unload routine for a regular driver is different from that of a minifilter. So, you need to use the signature required by filter manager model and simply do all the required cleanup in this unload routine.

Next step is to call FltRegisterFilter which will register the driver with the filter manager. And then you can simply create the communication port using the FltCreateCommunicationPort call. And then? Simply start firing requests as you do in the minifilter.

Couple of points to ponder and explore:

  1. What altitude should we ask from MS for this kind of a filter? We are not going to be layered between other drivers in the FS stack. Well, it doesn't really matter what altitude you get because altitudes define the order in which you will see the I/O requests with respect to other filters. So, simply ask for a activity monitoring range.
  2. What happens if we want to attach to a storage stack? The filter manager might not be avialable that soon. Well, think of it this way, you need to communicate with you service only when the service is up. Right? So, you can delay registering your driver with the filter manager by "somehow" detecting that the user mode environment is ready. Or, if it is acceptable for your driver's functionality, you can simply put a dependency on fltmgr.sys in your driver's registry entry.
  3. Implications of LoadOrderGroup and driver type.

Will try out more experiments for the above points and post them soon.

However, there is an alternate way of achieving this. Make a common communication driver that implements this functionalty and export various communication functions to be used by other drivers that you develop. And I like this one. This export driver can load after the filter manager is up. But your driver can load even before that. And anyways, you can be sure that your communication driver is up when you "actually" want to communicate with your service. Just remember to set the start type of your export driver appropriately.